Compromising Yourself with WinRM’s “AllowUnencrypted = True”

One thing that’s a mixed blessing in the world of automation is how often people freely share snippets of code that you can copy and paste to make things work.

Sometimes, this is a snippet of code / functionality that would have been hard or impossible to write yourself, and saves the day. Sometimes, this is a snippet that changes some configuration settings to finally make something work.

For both types of code, you should really understand what’s happening before you run it. Configuration snippets are particularly important in this regard, as they permanently change the posture of the system.

One disappointing example is the number of posts out there that show you how to enable CredSSP without ever discussing the dangers. They don’t tend to warn you that the CredSSP authentication mechanism essentially donates your username and password to the remote system – the reason we disable it by default.

So let’s talk about another example, where folks demonstrate how to easily connect to WinRM over SOAP directly.

winrm set winrm/config/client/auth @{Basic="true"}
winrm set winrm/config/service/auth @{Basic="true"}
winrm set winrm/config/service @{AllowUnencrypted="true"}

Hmm. That’s configuring a lot of non-default settings. And without any sort of security guidance. But whatever.

I can use pretty much any HTTP-aware tool to make calls now. Take an example of using a client that requires these settings, enumerating the ‘WinRM’ service from a remote computer. Here’s a network capture of that event:

image

The tool is using ‘Authorization: Basic’, as you can see from the top. The rest of the red is the content of the WinRM SOAP request.

The first thing you’ll notice is that this is a lot of unencrypted content. In fact, all of it. This command and response was over plain HTTP. If I was retrieving sensitive information from that remote computer, it is now public knowledge. This message also could have been tampered with in transit – either going there, or coming back. If an attacker intercepted this communication, they could have rewritten my innocent service request to instead add themselves to the local administrators group of that local machine.

There’s one particularly sensitive bit of information you may have noticed. The Authorization header:

Authorization: Basic RnJpc2t5TWNSaXNreTpTb21lIVN1cDNyU3RyMG5nUGFzc3coKXJk

If we research what that complicated string of text is, we’ll see that it’s just a Base64 encoding of the username and password, separated by a colon:

PS [C:\temp]
>> [System.Text.Encoding]::Ascii.GetString([Convert]::FromBase64String("RnJpc2t5TWNSaXNreTpTb21lIVN1cDNyU3RyMG5nUGFzc3coKXJk"))

FriskyMcRisky:Some!Sup3rStr0ngPassw()rd

Hope you didn’t need those credentials, because you just donated them!

Basic Authentication isn’t always the devil, as it can be done over a secure authenticated channel (like HTTPS). And HTTP isn’t always the devil, as it can be done over a secure authenticated channel (like Kerberos). But combine them (and disable all kinds of WinRM security safeguards), and you’re in for a bad day.

So please – if you are using code from others, make sure you understand what it does. Understanding code is much easier than writing it, so you’re still benefiting.

And blog / sample authors? Don’t think you’re getting away so easy 🙂 If you’re providing code samples that might have an unintended side effect (i.e.: complete system and credential compromise), please make those risks drastically clear. Saying “for testing purposes only” doesn’t count.

 

Lee Holmes [MSFT]
Principal Software Engineer