Tuesday 27 May 2014

Abusive Directory Syndrome

As ever there's been some activity recently on Full Disclosure where one side believes something's a security vulnerability and the other says it's not. I'm not going to be drawn into that debate, but one interesting point did come up. Specifically that you can't create files in the root of the system drive (i.e. C:\) as a non-admin user, you can only create directories. Well this is both 100% true and false at the same time, it just depends what you mean by a "file" and who is asking at the time.

The title might give the game away, this is all to do with NTFS Alternate Data Streams (ADS). The NTFS file system supports multiple alternative data streams which can be assigned to a file, this can be used for storing additional attributes and data out-of-band. For example it is used by Internet Explorer to store the zone information for a downloaded file so the shell can warn you when you try to execute a file from Internet. Streams have names and are accessed using a special syntax, filename:streamname. You can easily create a data stream using the command prompt, type the following (in a directory you can write to):

echo Hello > abc
echo World! > abc:stm

more < abc  - Prints Hello
more < abc:stm - Prints World!

Easy no? Okay lets try it in the root of the system drive (obviously as a normal user):

echo Hello > c:\abc:stm - Prints Foxtrot Oscar
Oh well worth a try, but wait there's more...  One of the lesser known abilities of ADS, except to the odd Malware author, is you can also create streams on directories. Does this create new directories? Of course not, it creates file streams which you can access using file APIs. Let's try this again:

mkdir c:\abc
echo Fucking Awesome! > c:\abc:stm
more < c:\abc - File can't be found
more < c:\abc:stm - Prints Fucking Awesome!

Well we've created something which looks like a file to the Windows APIs, but is in the root of the system drive, something you're not supposed to be able to do. It seems deeply odd that you can:

  • Add an ADS to a directory, and 
  • The ADS is considered a file from the API perspective

Of course this doesn't help us exploit unquoted service paths, you can't have everything. Still when you consider the filename from a security perspective it has an interesting property, namely that its nominal parent in the hierarchy (when we're dealing with paths that's going to be what's separated by slashes) is C:\. A naive security verification process might assume that the file exists in a secure directory, leading to a security issue.

Take for example User Account Control (UAC), better know as the "Stop with the bloody security dialogs and let me get my work done" feature which was introduced in Vista. The service which controls this (Application Info) has the ability to automatically elevate certain executables, for controlling the UI (UIAccess) or to reduce the number of prompts you see. It verifies that the executables are in a secure directory such as c:\windows\system32 but specifically excludes writeable directories such as c:\windows\system32\tasks. But if you could write to Tasks:stm then that wouldn't be under the Tasks directory and so would be allowed? Well let's try it!

echo Hello > c:\windows\system32\tasks:stm
more < c:\windows\system32\tasks:stm - Access Denied :(

Why does it do that? We only have to take a look at the DACL to find out why, we have write access to Tasks but not read:

c:\>icacls c:\Windows\system32\tasks
c:\Windows\system32\tasks BUILTIN\Administrators:(CI)(F)
                          NT AUTHORITY\SYSTEM:(CI)(F)
                          NT AUTHORITY\SYSTEM:(OI)(R,W,D,WDAC,WO)
                          NT AUTHORITY\Authenticated Users:(CI)(W,Rc)
                          NT AUTHORITY\NETWORK SERVICE:(CI)(W,Rc)
                          NT AUTHORITY\LOCAL SERVICE:(CI)(W,Rc)
                          CREATOR OWNER:(OI)(CI)(IO)(F)

Oh well... Such is life. We've managed to create the stream but can't re-read it, awesome Write Only Memory. Still this does demonstrate something interesting, the DACL for the directory is applied to all the sub-streams even though the DACL might make little sense for file content. The directory DACL for Tasks doesn't allow normal users to read or list the directory, which means that you can't read or execute the file stream.

In conclusion all you need to be able to create an ADS on a directory is write access to the directory. This is normally so you can modify the contents of the directory but it also applies to creating streams. But the immediate security principal for a stream then becomes the parent directory which might not be as expected. If I find the time I might blog about other interesting abuses of ADS at a later date.

Wednesday 21 May 2014

Impersonation and MS14-027

The recent MS14-027 patch intrigued me, a local EoP using ShellExecute. It seems it also intrigued others so I pointed out how it probably worked on Twitter but I hadn't confirmed it. This post is just a quick write up of what the patch does and doesn't fix. It turned out to be more complex than it first seemed and I'm not even sure it's correctly patched. Anyway, first a few caveats, I am fairly confident that what I'm presenting here is already known to some anyway. Also I'm not providing direct exploitation details, you'd need to find the actual mechanism to get the EoP working (at least to LocalSystem).

I theorized that the issue was due to mishandling of the registry when querying for file associations. Specifically the handling of HKEY_CLASSES_ROOT (HKCR) registry hive when under an impersonation token. When the ShellExecute function is passed a file to execute it first looks up the extension in the HKCR key. For example if you try to open a text file, it will try and open HKCR\.txt. If you know anything about the registry and how COM registration works you might know that HKCR isn't a real registry hive at all. Instead it's a merging of the keys HKEY_CURRENT_USER\Software\Classes and HKEY_LOCAL_MACHINE\Software\Classes. In most scenarios HKCU is taken to override HKLM registration as we can see in the following screenshot from Process Monitor (note PM records all access to HKLM classes as HKCR confusing the issue somewhat). 

When ShellExecute has read this registry key it tries to read a few values out it, most importantly the default value. The value of the default value represents a ProgID which determines how the shell handles the file extension. So for example the .txt extension is mapped to the ProgID 'txtfile'.

The ProgID just points ShellExecute to go reading HKCR/txtfile and we finally find what we're looking for, the shell verb registrations. The ShellExecute function also takes a verb parameter, this is the Action to perform on the file, so it could be print or edit but by far the most common one is open. There are many possible things to do here but one common action is to run another process passing the file path as an argument. As you can see below a text file defaults to being passed to NOTEPAD.

Now the crucial thing to understand here is that HKCU can be written to by a normal, unprivileged user. But HKCU is again a fake hive and is in fact just the key HKEY_USERS\SID where SID is replaced with the string SID of the current user (see pretty obvious, I guess). And even this isn't strictly 100% true when it comes to HKCR but it's close enough. Anyway, so what you might be asking? Well this is wonderful until user Impersonation gets involved. If a system or administrator process impersonates another user it's also suddenly finds when it accesses HKCU it really accesses the impersonated user's keys instead of its own. Perhaps this could lead to a system service that is  impersonating a user and then calls ShellExecute to start up the wrong handler for a file type leading to arbitrary execution at a higher privilege. 

With all this in mind lets take a look at patch in a bit more depth. The first step it to diff the patched binary with the original, sometimes easier said than done. I ran an unpatched and patched copies of shell32.dll through Patchdiff2 in IDA Pro which lead to a few interesting changes. In the function CAssocProgidElement::_InitFileAssociation a call was added to a new function CAssocProgidElement::SetPerMachineRootIfNeeded.

Digging into that revealed what the function was doing. If the current thread was impersonating another user and the current Session ID is 0 and the file extension being looked up is one a set of specific types the lookup is switched from HKCR to only HKLM. This seemed to confirm by suspicions that the patch targeted local system elevation (the only user on session 0 is likely to be LocalSystem or one of the service accounts) and it was related to impersonation.

Looking at the list of extension they all seemed to be executables (so .exe, .cmd etc.) so I knocked up a quick example program to test this out.

Running this program from a system account (using good old psexec -s) passing it the path to an executable file and the process ID of one of my user processes (say explorer.exe) I could see in process monitor it's reading the corresponding HKCU registry settings.

Okay so a last bit of exploitation is necessary I guess :) If you now register your own handler for the executable ProgID (in this case cmdfile), then no matter what the process executes it will instead run code of the attacker's choosing at what ever privilege the caller has. This is because Impersonation doesn't automatically cross to new processes, you need to call something special like CreateProcessFromUser to do that.

So how's it being exploited in the real world? I can't say for certain without knowing the original attack vector (and I don't really have the time to go wading through all the system services looking for the bad guy, assuming it's even Microsoft's code and not a third party service). Presumably there's something which calls ShellExecute on an executable file type (which you don't control the path to) during impersonating another user.

Still is it fixed? One thing I'm fairly clear on is there seems to still be a few potential attack vectors. This doesn't seem to do anything against an elevated admin user's processes being subverted. If you register a file extension as the unprivileged user it will get used by an admin process for the same user. This is ultimately by design, otherwise you would get inconsistent behaviour in elevated processes. The fix is only enabled if the current thread is impersonating and it's in session 0 (i.e. system services), and it's only enabled for certain executable file types.

This last requirement might seem odd, surely this applies to any file type? Well it does in a sense, however the way ShellExecute works is if the handling of the file type might block it runs the processing asynchronously in a new thread. Just like processes, threads don't inherit impersonation levels so the issue goes away. Turns out about the only thing it treats synchronously are executables. Well unless anyone instead uses things like FindExecutable or AssocQueryString but I digress ;-) And in my investigation I found some other stuff which perhaps I should send MS's way, let's hope I'm not too lazy to do so.