Our Blog

Is TLS more secure? The WinRMS case.

Reading time ~10 min

0/ TL;DR


WinRM is protected against NTLMRelay as communications are encrypted. However WinRMS (the one communicating over HTTPS) is not entirely. That said, WinRMS is not configured on a default server installation (while WinRM is). So, if someone tried to harden their servers’ configurations (by removing the HTTP endpoint), they would open a new possible target that can be used to relay HTTP/SMB and LDAP NTLMv1 only authentications to WinRMS and thus gain remote code execution.

I/ Stupidest idea becoming an actual thing

At the end of my talk at Insomni’hack 2025 about Browser Cache Smuggling, I said that “most of the time, huge innovations come from the stupidest and/or smallest idea”. What you are going to read is a perfect example of these words.

Six months ago I was doing an internal assessment in which the client had a super strong security baseline. No known CVE’s exposed, no relay possibilities… Nothing that could be used. Except a missconfigured server that was administrator of another one (that’s a classic SCCM configuration). Thing is, even if you can trigger an authentication from a server to another one, it won’t be useful if:

  • SMB and LDAP are signed ;
  • LDAP channel binding is set ;
  • ESC8 is patched.

Turns out, all of these protections were set… Is it game over ? Well yes but no. A client may be secured against known exploits but what about those even us, hackerz, are not yet aware of ? That in mind, I started looking for new potential exploits.

Especially there are two things we deeply like for internal AD assessments:

  • Ways of coercing a HTTP authentication (such as webdav) since those ones do not support signing/channel binding ;
  • Endpoints to which we can coerce HTTP authentication since, most of the time they are not protected either!

And now let me introduce our new target:

WinRM!

II/ What’s WinRM

WinRM (Windows Remoting) is a specific protocol used to manage Windows computers and servers through an API exposed on a web server. WinRM comes with two versions:

  • Classic WinRM exposed on port 5985;
  • TLS encapsulated WinRM exposed on port 5986.

From a hacker point of view, WinRM looks like a promising target as it is exposed unencrypted. As such, it should be possible to relay SMB/LDAP/HTTP NTLM authentication to that endpoint. And it worked… Until I realise that even if WinRM does expose itself on HTTP, its communications are actually encrypted:

Sucks right? Sucks even more when I realised that encryption is done via a key derived from the user’s password… Naively I thought that WinRMS would be protected too, but as it turns, data is not encrypted inside the TLS tunnel! I mean, why would they? TLS is already encrypted stuff. Last thing I needed to know is whether channel binding is enabled on WinRMS. People said yes but ultimately I asked the question on Twitter and @filip_dragovic replied with this screenshot:

This is quite interesting because the paper says that relay to WinRMS should not work, even thought it also says that Channel Binding (CBT) is set to “Relaxed” in the WinRM configuration. However, Channel Binding, which is used to protect NTLM authentication by binding it to a TLS session, can be configured with one of the following three values:

  • None: Channel Binding is not supported (that’s the default configuration for ADCS HTTPS web enrolment) ;
  • Strict: Channel Binding is required. If not sent by the client, the server must NOT process the request ;
  • Relaxed: Channel Binding is optional. If the client sends it then the server protects the communication otherwise it does not.

Sounds interesting right? :D

III/ Weaponizing the relay

That information got me thinking that there is something we can do here. But I forgot about it until @EAGAIIN asked for any new information, which ultimately led me into getting back to that subject. All I had to do was to create a new relay server/client for WinRM on NTLMRelayX which I started doing… Until I found foofus’s github repo which already provided most of the things I needed! So I’m not going deep dive into the internals of NTLMRelayX because most of it is really all about catching the NTLM authentication and forwarding it to another target. However, let’s check what the WinRM protocol looks like.

First of all, WinRM is a protocol that relies on, mostly, four XML files. The first one is used to initiate a “ShellId” that will be used in all our future calls:

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope
    xmlns:env="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
    xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"
    xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">

    <env:Header>
        <a:To>http://windows-host:5985/wsman</a:To>
        <a:ReplyTo>
            <a:Address mustUnderstand="true">
                http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
            </a:Address>
        </a:ReplyTo>
        <a:MessageID>uuid:2a8ac24f-00f0-4a87-860c-bf58d33a1e0a</a:MessageID>
        <a:Action mustUnderstand="true">
            http://schemas.xmlsoap.org/ws/2004/09/transfer/Create
        </a:Action>
        <w:ResourceURI mustUnderstand="true">
            http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
        </w:ResourceURI>
        <w:OperationTimeout>PT20S</w:OperationTimeout>
        <w:MaxEnvelopeSize mustUnderstand="true">153600</w:MaxEnvelopeSize>
        <w:OptionSet>
            <w:Option Name="WINRS_NOPROFILE">FALSE</w:Option>
            <w:Option Name="WINRS_CODEPAGE">437</w:Option>
        </w:OptionSet>
        <w:Locale xml:lang="en-US"/>
        <p:DataLocale xml:lang="en-US"/>
    </env:Header>

    <env:Body>
        <rsp:Shell>
            <rsp:InputStreams>stdin</rsp:InputStreams>
            <rsp:OutputStreams>stdout stderr</rsp:OutputStreams>
        </rsp:Shell>
    </env:Body>
</env:Envelope>

Nothing interesting in that file to me. The only thing to remember is that sending that XML content will end up in the server sending you back a ShellID (a UUID). Now that you have that ID, you can send another XML file in which you will provide the command you want to launch:

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope
    xmlns:env="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
    xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">

    <env:Header>
        <a:To>http://windows-host:5985/wsman</a:To>
        <a:ReplyTo>
            <a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
        </a:ReplyTo>
        <a:Action mustUnderstand="true">
            http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command
        </a:Action>
        <a:MessageID>uuid:10000000-0000-0000-0000-000000000002</a:MessageID>
        <w:ResourceURI mustUnderstand="true">
            http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
        </w:ResourceURI>
        <w:SelectorSet>
            <w:Selector Name="ShellId">SHELLID</w:Selector>
        </w:SelectorSet>
    </env:Header>

    <env:Body>
        <rsp:CommandLine>
            <rsp:Command>whoami</rsp:Command>
        </rsp:CommandLine>
    </env:Body>
    </env:Envelope>

The server will send a command ID, that will then allow you to ask where you want to get the result of that command, with the following XML request:

<?xml version="1.0" encoding="utf-8"?>
<env:Envelope
    xmlns:env="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
    xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell">

    <env:Header>
        <a:To>http://windows-host:5985/wsman</a:To>
        <a:ReplyTo>
            <a:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
        </a:ReplyTo>
        <a:Action mustUnderstand="true">
            http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive
        </a:Action>
        <a:MessageID>uuid:2a8ac24f-00f0-4a87-860c-bf58d33a1e0a</a:MessageID>
        <w:ResourceURI mustUnderstand="true">
            http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd
        </w:ResourceURI>
        <w:SelectorSet>
            <w:Selector Name="ShellId">SHELLID</w:Selector>
        </w:SelectorSet>
    </env:Header>

    <env:Body>
        <rsp:Receive>
            <rsp:DesiredStream CommandId="COMMANDID">stdout stderr</rsp:DesiredStream>
        </rsp:Receive>
    </env:Body>
</env:Envelope>

And then you’ll get the result of the command:


Erratum

When developping the WinRM module for NTLMRelayX I didn’t not notice that my test domains were configured to authorize NTLMv1 authentications.

That changes EVERYTHING for a simple reason: NTLMv1 does not support signing/CBT. And this is why the POC you’ll see later works. Now NTLMv2 does support these mechanisms and since Windows SMB clients do ask for Channel Binding, we will not be able to strip that off (like we used when drop the mic was found) and thus won’t be able to relay to WinRMS.

Now that doesn’t necessarily mean the technique itself is to be thrown away. Indeed this module can still be used when:

  • NTLMv1 is enabled on the domain (whether it is natively or because of a downgrade attack using tools such as RemoteMonologue) ;
  • Someone connects to our WinRM server directly (this can be done via a MITM attack) ;
  • Someone gets relayed from a browser that does not support Channel Binding (firefox for example)
  • The WinRMS service is configured with Channel Binding set to None (shouldn’t be that often but hey who knows ?).

As such I apologise for the fake news I spread. NTLM Relay from SMB to WinRMS, as @D1iv3 mentioned in his slides, is not possible on a default windows installation. I have learnt from that rookie mistake and will make sure to better check the results of my future work before publishing next time!


At this point we are able to relay NTLM (Erratum: NTLMv1 only) authentication and run commands on the remote target via the XML files. Thing is, when relaying authentication, you are not always sure of whose you are going to be able to relay. As such, it was not possible to create a default action when relaying a target to WinRM. Instead, I decided the default behaviour is to create an interactive shell which will allow you to run commands remotely depending on what you want to do. Below is the demo of the final tool:

IV/ Caveat and blueteaming

Although this relay technique is interesting, it’s important to mention that WinRMS is not running by default, even when enabling remote desktop functionalities. To enable the WinRMS service, the following commands will have to be launched:

# Generates a self signed TLS certificate (you can also use your certificate service)
New-SelfSignedCertificate -Subject 'CN=server_name.domain.com' -TextExtension '2.5.29.37={text}1.3.6.1.5.5.7.3.1'

# Creates a new HTTPS listener that uses the certificate created before
winrm create winrm/config/Listener?Address=*+Transport=HTTPS '@{Hostname="ServerB.domain.com"; CertificateThumbprint="<cert thumbprint here>"}'

# Opening port 5986 on the internal firewall
$FirewallParam = @{
    DisplayName = 'WinRM HTTPS'
    Direction = 'Inbound'
    LocalPort = 5986
    Protocol = 'TCP'
    Action = 'Allow'
    Program = 'System'
}
New-NetFirewallRule @FirewallParam

As I mentioned before, Channel Binding is not enforced by default on the HTTPS listener as the following command shows:

winrm get winrm/config/service/auth

Which is quite ironic considering that a sysadmin trying to harden their domain by removing HTTP endpoints, will actually activate a protocol that is not secured by default and will allow relaying NTLM authentication to it… So yeah more security actually bring less security (which kind of reminds me of the “restrictedAdmin” thing)…

Hopefully, fixing that is quite easy, all you’ll have to do is to enable Channel Binding over the HTTP listener using the following PowerShell command:

winrm set winrm/config/service/auth '@{CbtHardeningLevel="Strict"}'

And relaying won’t be possible anymore:

Before closing that blogpost, I wanted to thank Joe, Foofus, who developed the foundation for that NTLMRelayx’s plugin, Github is really full of very interesting researches, tools and people :P! The PR is already pushed on the impacket repo :)

Happy hacking!

This is a cross-post blog post from https://blog.whiteflag.io.