Monday, March 24, 2014

Using an SSP/AP LSASS Proxy to Mitigate Pass-the-Hash Pre-Windows 8.1

mit·i·gate
verb
  1. 1.
    make less severe, serious, or painful.
    "he wanted to mitigate misery in the world"
    synonyms:alleviatereducediminishlessenweakenlightenattenuate, take the edge off, allayeaseassuagepalliaterelieve, tone down 


Intro

A colleague (Matt Weeks/scriptjunkie) guest posted an article on the passing-the-hash blog (@passingthehash) about March being Pass-the-Hash awareness month, updating us on where we are at today regarding the family of issues.  I thought this would be a good subject for an opening post.  This post mostly concerns pre-Windows 8.1 systems that use Smart Cards and Kerberos as their primary form of authentication.  It may apply to other configurations as well.

This post covers the very basics of what I did to create a custom Security Support Provider/Authentication Package (SSP/AP) as a proxy in order to help mitigate the problem of LSASS storing NTLM credentials in its memory space.  This technique should probably only be used when your primary mode of authentication is something other than NTLM, such as Kerberos, as it will prevent LSASS from properly caching NTLM credentials on the client for later use.  While this does not solve the problem and is by no means a perfect solution (that probably has to come from Microsoft), it will at least offer some protection against some of the low hanging fruit attacks.


Kerberos and NTLM Hashes

Many believe that the solution to Pass-the-Hash is to simply require Kerberos on their network for authentication, thus moving the problem to Pass-the-Ticket.  What they don't realize is that the system also generates an NTLM hash that is stored in the LSASS memory, even when it is not used.  Without going into too much detail, as its documented elsewhere (see references below) the gist is that the key distribution center provides a hash of the user's credentials to be stored by the client's LSASS in case Kerberos is later unavailable, in which case the client will revert to using the hash for single-sign-on logins/authentication.

Because of this, even on a domain that requires Kerberos to login and access network resources, a backup hash is readily available for stealing.  The end effect is that, from an attacker's perspective, not much has changed.  Any local logins will have their user credential hash stored in the LSASS memory for at least some time as will any remote desktop logins to that system (such as domain admins/privileged users).  All the attacker needs to do is grab those hashes from LSASS memory and continue.  This was the problem that we were trying to mitigate on the client machines.

We'll come back to this...


SSP/AP and SSP Proxies


What are SSP/APs and SSPs?

SSP/APs and SSPs are described on Microsoft's SSP/APs vs SSPs page, but basically they are packages (DLLs) that are loaded by LSASS upon start which can be used for various security mechanisms such as authentication, message integrity, and encryption.  Microsoft provides a couple with Windows, to include msv1_0.dll  (local interactive logins and NTLM authentication, among others) and kerberos.dll (Kerberos).

Microsoft also allows custom SSP/APs and SSPs to be registered and loaded by LSASS as such:

To register an SSP/AP, add the name of the package to
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Authentication Packages.

To register an SSP, add the name of the package to
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Security Packages.

For each package registered, a DLL with that package name (ex: mypackage = mypackage.dll) must exist in the Windows\System32 directory.  Once the system restarts, LSASS will load the packages found in those two registry keys and use them as described in the SSP/AP and SSP documentation.


What is an SSP/AP or SP Proxy?

While researching the LSASS authentication flow and components, I came across a blog by Koby Kahane (Implementing an LSA proxy authentication package) describing how he implemented an LSA proxy authentication package.  He did quite a bit of research into proxying msv1_0 in order to test the feasibility of adding additional authentication steps prior to passing control to the original package.  When I saw this, one of the first things to come to mind was how could I use this technique to stop LSASS from caching credentials that I did not want used on the domain.


Authentication Flow

The flow of control through MSV1_0 and Kerberos isn't really documented very much (none officially), so I had to start mostly from scratch in order to identify where the credentials passed in order to determine the easiest locations to grab them.  The key function appeared to be LsaApLogonUserEx2 in both Kerberos and MSV1_0.  This is where much of the original work is done and where the credentials are returned from, initially.

Once the call is made into Kerberos at this function, the credentials are gathered and returned back out to LSASS.  Supplemental credentials gathered at this point will be sent to the appropriate security packages registered to handle them (such as MSV1_0 for NTLM) via the SpAddCredentials function.  There they are processed by their respective security packages and at a point near the end are fired off to LSASS via the AddCredential function. Note: The order of those two may be reversed as it has been quite some time since I did this.

This gave me several points in the chain where I was able to intercept and modify the hash in order to make it unusable before it was cached.

Building and Using the Proxies

After a colleague and I combed through the information, I decided that I could create a custom proxy in order to keep LSASS from caching the NTLM hashes that Kerberos added as supplemental credentials.

There were two places of interest where I could have scrambled the incoming NTLM hash before it was stored.  First, I could proxy the Kerberos module and scramble the NTLM hash before it was sent back to LSASS and into the MSV1_0 module or I could catch it as it was coming into MSV1_0 from Kerberos (as well as from any other source).  I decided that I would create a proxy for each 'just in case' and then choose one of them after testing, as I felt that the less I messed around with Microsoft's subsystems the less chance something would go wrong.

I created two proxies, one for the SSP/AP msv1_0.dll and another for the SSP kerberos.dll.  I really needed to handle two different sets of calls.  The first set was through the recommended use of the SpLsaModeInitialize and SpUserModeInitialize functions as described by Koby and the MSDN documentation, requiring you to create and populate the function tables accordingly, replacing any proxied functions with your own.  The second method was by exporting and proxying the functions available in the original DLLs (such as through jmp tables initialized upon load) for any application that may bypass the tables entirely (don't always count on other programs to do things the way they are supposed to do them).

MSV1_0

For the MSV1_0 proxy, the SpAddCredentialsPackage function needed to be proxied by saving the original pointer and placing a pointer to the new one into the returned function table in its place.  This is where the incoming NTLM credentials would be intercepted and modified before being passed onto the real MSV1_0 (via its SpAddCredentialsPackage) and stored/cached.

In the new function, I wanted to be selective about which logon types for which I scrambled the credentials as I still wanted local services to be able to start up and authenticate with the local system.  To do this, I checked the incoming SECURITY_LOGON_TYPE parameter for the service type (or any other type that you want to allow to cache NTLM credentials - preferably only local accounts) and just pass on through to the original function then return.

For everything else, I grabbed the PSECPKG_SUPPLEMENTAL_CRED structure and verified that the package name was "NTLM".  If this was the case, I scrambled the LmPassword and NtPassword parts of that structure.  Once that is done I just called the original function, passing in the scrambled supplemental credentials to the real MSV1_0.  MSV1_0 takes them and LSASS will cache the useless hash into its memory.

KERBEROS

For the KERBEROS proxy (and the MSV1_0 proxy if you wish to also handle the hash coming from an interactive login at an earlier point in the process), I proxied and modified LsaApLogonUserEx2.  In this case, I called the original as normal and intercepted the credentials being returned from that call.  At that point, I used the same logic to check and scramble the supplemental (NTLM) credentials before returning.

One other place that the credentials can be intercepted is at the point just prior to going into LSASS itself.  This requires intercepting the proxied package's call to LASS' AddCredentials function, which is passed in via a function table during SpInitialize.  In this case, the AddCredentials pointer is replaced by a pointer to a new function before passing the table to the original package such that when the original package calls that function to add credentials, it goes through the proxied function first.  In the proxied function, the credentials need to be unwrapped via LsaUnprotectMemory, scrambled, and then re-wrapped via LsaProtectMemory before being passed into LSASS.  This should not be necessary, but can be used as a failsafe.

Using the Proxies

After the proxies were completed, they needed to be put to use.  There are two methods of doing this with pros and cons to each.  First, one can simply rename the DLL being proxied to something else while using the real name (msv1_0.dll or kerberos.dll) for the proxy.  Second, the original DLLs can keep the same name, using different names for the proxies while changing the registry entries for the Security Packages and/or Authentication Packages.  For the former, the problem one may run into is having the DLLs modified when a patch/update comes down the line intended for Microsoft's DLLs.  For the latter, someone with access to view those registry keys may notice the names are not what they were supposed to be.


Choosing a Proxy

In the end I chose to use the MSV1_0 proxy over the Kerberos proxy in order to catch hashes that may come from other packages/programs.  When a set of credentials comes into LSASS, it is sent to the appropriate package for handling.  In the case of NTLM credentials, they are sent to the MSV1_0 package.  You could easily switch to using the Kerberos proxy, catching and scrubbing the supplemental credentials after returning from kerberos.dll in LsaApLogonUserEx2().  This, however, will not catch any NTLM hashes coming from outside of Kerberos.  Alternatively, one could use both proxies as opposed to the minimum required to catch most cases.

Summary

With this technique, a good majority of automated/default attacks can be mitigated against Pass-the-Hash.  While there is still a hash cached/stored in memory by LSASS, that hash has been scrambled and is therefore useless.  If your systems use NTLM authentication then it probably means that SSO will no longer work resulting in endless login prompts when attempting to use remote resources.  It's really only useful for networks that want to force Kerberos at the expense of legacy compatibility.

Also, I need to reiterate that this does not solve the whole problem.  There are other issues which I will not go into but my team had to solve such as protecting the proxies from attack.  A sophisticated attacker has several methods in which this can be easily defeated if it is not protected.  The proxies do absolutely nothing to mitigate an attacker intercepting credentials in other ways such as across the network or Pass-the-Ticket.  It does help narrow the available target a little bit, and every little bit helps.

One final note to keep in mind for anyone following in these footsteps.  Mucking around with LSASS can get pretty messy.  If you get something wrong you will not know until you've already told LSASS to load up your modules, in which case a crash of LSASS (assuming you don't blue screen) can prevent you from logging into that system again normally.  I would advise that you test your development thoroughly using a VM, and once you do try it on a live system be prepared to revert, use a boot disc, or mount the hard drive via another system to fix it.  Take heed of that if you are deploying it across a domain.

I hope someone finds this useful.  It's primarily targeted towards Windows XP and Windows 7, but may continue to apply to Windows 8 as well depending on how Microsoft works the Pass-the-Hash issue.

-=[Kevin]=-




Addendum A - DeleteSecurityPackage

During my research and development relating to using SSP/APs and SSPs, I came across an issue with DeleteSecurityPackage that threw me for a loop.  I was working on a system that would remove unauthorized Security Packages on a live system.  Research led me to this function in the Windows security API - a function that was documented on MSDN as:

Deletes a security support provider from the list of providers supported by Microsoft Negotiate.

Attempting to use this function, however, failed.  Upon use, the return value from the function was 0x80090302 (SEC_E_UNSUPPORTED_FUNCTION).  That didn't seem right since it was documented and I had not found anything while searching the internet from anyone else to say otherwise.  After double checking and ensuring that there was no error on my part I broke out my trusty IDA Pro and decided to trace the function back to its roots.  After following several proxied jumps I got to the source function DeleteSecurityPackageW (DeleteSecurityPackageA also redirects to this function).

Here is what I found:













Apparently either Microsoft forgot to implement the function, or more likely discovered that it was a pretty hard problem to tackle without making any assumptions for the end user and decided not to implement it (while leaving the documentation online).  To give them some credit, it does seem like it would be a difficult problem - when you remove a security package currently in use, what is your default policy for dealing with accounts logged in using that package?  Do you kick them, keep them logged in (if that's possible), prevent removal until they log out?

Maybe they decided it was better to just avoid the question altogether.  It's not entirely bad that it's not implemented.  It means that in order to remove a security package (such as to replace it), typically a reboot is going to be required as LSASS will force one if you try to remove it any other way.  Who knows? 

Anyway, I e-mailed msdn support and posted a notice on the msdn documentation page, but never got a response... so there it is if anyone else is having an issue using it.


Addendum B - EnumerateSecurityPackages

Another issue that I came across while working with the Windows security APIs relating to LSASS was while using EnumerateSecurityPackages.  I needed to use this function in order to monitor what security packages were loaded in order to detect if any were unauthorized.  Initially, when I tested this function manually, everything was working fine.  Each time I ran my test it showed me all of the currently loaded security packages, including any that I manually loaded after boot using AddSecurityPackage.

Once I automated the process to periodically call the function I started seeing problems.  What would happen is when a process first called this function it would populate a list with all of the security packages that it saw loaded.  Subsequent calls would return the very same list.  I double checked and was using FreeContextBuffer just as the documentation specified, but nothing changed.  This was a rather annoying issue that I had to somehow overcome.

My solution (and my recommended solution if it has not been fixed yet) was to embed a second binary into the parent binary which would be dropped and spawned by the parent each time it needs to be checked.  The output of the spawn is easily wrapped and captured by the parent.  The spawn can either be a temporary drop or a permanent drop executed periodically by the parent monitoring system.  Since only the parent is constantly running, the spawn only needs to make that call and grab the list once per execution.

Another e-mail and another post to the documentation on msdn without a response.  It may or may not have been fixed by now, but just in case, here's one workaround.


References


Koby Kahane, 2008
http://kobyk.wordpress.com/2008/08/30/implementing-an-lsa-proxy-authentication-package/

SANS Institute
Pass-the-hash attacks: Tools and Mitigation
Bashar Ewaida, 2010

Kerberos Working Group
Johansson, 2009

Core Labs
Hernan Ochoa

...and my former team at MacAulay-Brown...

EDITS:
20140327
Added the definition of mitigation to the top in order to prevent any misunderstandings.
Added the twitter handle for @passthehash in the introduction.

No comments:

Post a Comment