Sunday 5 November 2017

Named Pipe Secure Prefixes

When writing named pipe servers on Windows it’s imperative is do so securely. One common problem you’ll encounter is named pipe squatting, where a low privileged application creates a named pipe server either before the real server does or as a new instance of an existing server. This could lead to information disclosure if the server is being used to aggregate private data from a number of clients as well as elevation of privilege if the conditions are right.


There’s some programming strategies to try and eliminate named pipe squatting including passing the FILE_FLAG_FIRST_PIPE_INSTANCE flag to CreatedNamedPipe which will cause an error if the pipe already exists as well as appropriate configuration of the pipe’s security descriptor. A recent addition to Windows is named pipe secure prefixes, which make it easier to develop a named pipe server which isn’t vulnerable to squatting. Unfortunately I can’t find any official documentation on these prefixes or how you use them. Prefixes seem to be mentioned briefly in Windows Internals, but it doesn’t go into any detail about what they are or why they work. So this blog is an effort to remedy that lack of documentation.


First let’s start with how named pipes are named. Named pipes are exposed by the Named Pipe File System (NPFS) driver, which creates the \Device\NamedPipe device object. When you create a new named pipe instance you call CreateNamedPipe (really NtCreateNamedPipeFile under the hood) with the full path to the pipe to create. It’s typically to see this in the form \\.\pipe\PipeName where PipeName is the name you want to assign. At the native API level this path is converted to \??\pipe\PipeName; \??\pipe is a symbolic link which ultimately resolves this path to \Device\NamedPipe\PipeName.


Even though \Device\NamedPipe is a file system, NPFS doesn’t support directories other than the root. If you list the contents of the named pipe root directory you’ll notice that some of the names pipes have backslashes in the name, however NPFS just treats them as names as shown below.


named_pipes.PNG


So we’d assume we can’t have directories, but if we look at the function which handles IRP_MJ_CREATE dispatch in the NPFS driver we find something interesting:


NTSTATUS NpFsdCreate(PDEVICE_OBJECT device, PIRP irp) {
 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(irp);
 BOOLEAN directoryfile = stack->Parameters.Create.Options
                                & FILE_DIRECTORY_FILE;
 DWORD disposition = stack->Parameters.Create.Options >> 24;
 NTSTATUS status;
 // Other stuff...

 if (directoryfile) { ← Check for creating a directory
   if (disposition == FILE_OPEN) {
     status = NpOpenNamedPipePrefix(...);
   } else if (disposition == FILE_CREATE
           || disposition == FILE_OPEN_IF) {
     status = NpCreateNamedPipePrefix(...);
   }
 }

 // Even more stuff...
}


In the code we can see that the flag for creating a directory file is checked. If a directory file is requested then the driver tries to create or open a named pipe prefix. Let’s try and create one of these prefixes:


create_prefix.PNG


Well darn, it say it requires a privilege. Let’s dig into NpCreateNamedPipePrefix to see which privilege we’re missing.


NTSTATUS NpCreateNamedPipePrefix(...) {  
 // Blah blah...
 if (SeSinglePrivilegeCheck(SeExports->SeTcbPrivilege, UserMode)) {
   // Continue...
 } else {
   return STATUS_PRIVILEGE_NOT_HELD;
 }
}


So that’s awkward, TCB privilege is only granted to SYSTEM users, not even to administrators. While as an administrator it’s not that hard to get a SYSTEM token the same can’t be said of LocalService or NetworkService accounts. At least let’s check if impersonating SYSTEM with TCB privilege will work to create a prefix:


create_prefix_working.PNG


We now have a new prefix called Badgers, so let’s try and create a new named pipe as a normal user under that prefix and see if it does anything interesting.


create_pipe_access_denied.PNG


We get STATUS_ACCESS_DENIED returned. This it turns out is because the driver will find the largest prefix that’s been registered and check if the caller has access to the prefix’s security descriptor. What’s the security descriptor for the Badgers prefix?
prefix_sd.PNG


Seems it’s just SYSTEM and Administrators group with access, which makes sense based on the original caller being SYSTEM. Therefore, if we supply a more permissive security descriptor then it should allow a normal user to create the pipe.


create_with_dacl.PNG


Another question, how can we get rid of an existing prefix? You just need to close all handles to the prefix and it will go away automatically.


As said it’s a pain that you need TCB privilege to create new secure prefixes especially for non-administrator service accounts. Has the system created any prefixes already? There’s nothing obvious in NPFS. After a bit of investigation the Session Manager process (SMSS) creates a number of known prefixes in the function SmpCreateProtectedPrefixes. For example SMSS creates the following prefixes:


\ProtectedPrefix\Administrators
\ProtectedPrefix\LocalService
\ProtectedPrefix\NetWorkService


Each of these prefixes have a DACL based on their name, e.g. LocalService has a DACL which only allows the LocalService user to create named pipes under that prefix.
prefix_sd-2.PNG


It’s worth noting that the owner for the prefixes is the Administrators group which means an administrator could open the prefixes and rewrite the DACL, if you really wanted to screw with the OS :-)

Anyway, if you’re writing a new named pipe server and you want to make it more difficult for named pipe squatting then adding an appropriate secure prefix will prevent other users, especially low privileged users from creating a new pipe with the same name. If someone knows where this is documented please let me know as I think it’s a useful security feature which few know about.

Sunday 8 October 2017

Bypassing SACL Auditing on LSASS

Windows NT has supported the ability to audit resource access from day one. Any audit event ends up in the Security event log. To enable auditing an administrator needs to configure which types of resource access they want to audit in the Local or Group security policy, including whether to audit success and failure. Each resource to audit then needs to have a System Access Control List (SACL) applied which determines what types of access will be audited. The ACL can also specify a principal which limits the audit to specific groups.


My interest was piqued in this subject when I saw a tweet pointing out a change in Windows 10 which introduced a SACL for the LSASS process. The tweet contains a screenshot from a page describing changes in Windows 10 RTM. The implication is this addition of a SACL was to detect the use of tools such as Mimikatz which need to open the LSASS process. But does it work for that specific goal?


Let’s take apart this SACL for LSASS, what it means from an auditing perspective and then go into why this isn’t a great mechanism to discover Mimikatz or similar programs trying to access the memory of LSASS.


Let’s start by setting up a test system so we can verify the SACL is present, then enable auditing to check that we get auditing events when opening LSASS. I updated one of my Windows 10 1703 VMs, then installed the NtObjectManager PowerShell module.


A few things to note here, you must request the ACCESS_SYSTEM_SECURITY access right when opening the process otherwise you can’t access the SACL. You must also explicitly request the SACL when access the process’ security descriptor. We can see the SACL as an SDDL string, which matches with the SDDL string from the tweet/Microsoft web page. The SDDL representation isn’t a great way of understanding a SACL ACE, so I also expand it out in the middle. The expanded form tells us the ACE is an Audit ACE as expected, that the principal user is the Everyone group, the audit is enabled for both success and failure events and that the mask is set to 0x10.


Okay, let’s configure auditing for this event. I enabled Object Auditing in the system’s local security policy (for example run gpedit.msc) as shown:


You don’t need to reboot to change the auditing configuration, so just reopen the LSASS process as we did earlier in PowerShell, we should then see an audit event generated in the security event log as shown:



We can see that the event contains the target process (LSASS) and the source process (PowerShell) is logged. So how can we bypass this? Well let’s look back at what the SACL ACE means. The process the kernel goes through to determine whether to generate an audit event based on a SACL isn’t that much different from how the DACL is used in an access check. The kernel tries to find an ACE with a principal which is in the current token’s groups and the mask represents one or more access rights which the opened handle has been granted. So looking back at the SACL ACE we can conclude that the audit event will be generated if the current token has the Everyone group and the handle has been granted access 0x10. What’s 0x10 when applied to a process? We can find out using the Get-NtAccessMask cmdlet.


PS C:\> Get-NtAccessMask -AccessMask 0x10 -ToSpecificAccess Process
VmRead


This shows that the access represents PROCESS_VM_READ, which makes sense. If you’re trying to block a process scraping the contents of LSASS the handle needs that access right to call ReadProcessMemory.


The first thought for bypassing this is can you remove the Everyone group from your token and then open the process, at which point the audit rule shouldn’t match? Turns out not easily, for a start the only easy way of removing a group from a token is to convert it into a Deny Only group using CreateRestrictedToken. However, the kernel treats Deny Only groups as enabled for the purposes of auditing access checks. You can craft a new token without the group if you have SeCreateTokenPrivilege but it turns out that based on testing that the Everyone group is special and it doesn’t matter what groups you have in your token it will still match for auditing.


So what about the access mask instead? If you don’t request PROCESS_VM_READ then the audit event isn’t triggered. Of course we actually want that access right to do the memory scraping, so how could we get around this? One way is you could open the process for ACCESS_SYSTEM_SECURITY then modify the SACL to remove the audit entry. Of course changing a SACL generates an audit event, though a different event ID to the object access so if you’re not capturing those events you might miss it. But it turns out there’s at least one easier way, abusing handle duplication.


As I explained in a P0 blog post the DuplicateHandle system call has an interesting behaviour when using the pseudo current process handle, which has the value -1. Specifically if you try and duplicate the pseudo handle from another process you get back a full access handle to the source process. Therefore, to bypass this we can open LSASS with PROCESS_DUP_HANDLE access, duplicate the pseudo handle and get PROCESS_VM_READ access handle. You might assume that this would still end up in the audit log but it won’t. The handle duplication doesn’t result in an access check so the auditing functions never run. Try it yourself to prove that it does indeed work.



Of course this is just the easy way of bypassing the auditing. You could easily inject arbitrary code and threads into the process and also not hit the audit entry. This makes the audit SACL pretty useless as malicious code can easily circumvent it. As ever, if you’ve got administrator level code running on your machine you’re going to have a bad time.

So what’s the takeaway from this? One thing is you probably shouldn’t rely on the configured SACL to detect malicious code trying to exploit the memory in LSASS. The SACL is very weak, and it’s trivial to circumvent. Using something like Sysmon should do a better job (though I’ve not personally tried it) or enabling Credential Guard should stop the malicious code opening LSASS in the first place.

UPDATE: I screwed up by description of Credential Guard. CG is using Virtual Secure Mode to isolate the passwords and hashes in LSASS from people scraping the information but it doesn't actually prevent you opening the LSASS process. You can also enable LSASS as a PPL which will block access but I wouldn't trust PPL security.

Friday 25 August 2017

Accidental Directory Stream

It’s a well known fact that interface layers are a good source of bugs, and potentially security vulnerabilities. A feature which makes sense at the time of development might come back as a misfeature in subsequent years due to layers built above the feature. This blog post will describe one such weird edge case in file path handling on Windows. This edge case is very much in the category of "interesting" but not necessarily "useful" from a security perspective. If anyone thinks of a good use for it, let us all know :-)

Let's start with a simple bit of C++ code:

BOOL OpenFile(LPCWSTR filename) {
 HANDLE file = CreateFileW(filename, GENERIC_READ,
   FILE_SHARE_READ, nullptr, CREATE_ALWAYS, 0, nullptr);
 if (file == INVALID_HANDLE_VALUE)
   return FALSE;

 CloseHandle(file);
 return TRUE;
}

Nothing too strange here, OpenFile is just a wrapper around CreateFile. The purpose is to create a new file with a specified name and report TRUE if the creation was successful or FALSE if it was not. Now we need to something to call OpenFile.

void Test(LPCWSTR filename) {
 if (!OpenFile(filename))
   wcout << L"Error (base) - " << filename << endl;
 else
   wcout << L"Success (base) - " << filename << endl;

 WCHAR full_path[MAX_PATH];
 if (!GetFullPathNameW(filename, MAX_PATH, full_path, nullptr)) {
   wcout << L"Error getting full path" << endl;
   return;
 }

 if (!OpenFile(full_path))
   wcout << L"Error (full) - " << filename << endl;
 else
   wcout << L"Success (full) - " << filename << endl;
}
The Test function calls opens a file twice. First it just uses the base filename passed to the function. The base filename is then converted to a full path and the file is opened again. If there’s no funny stuff then the two open calls should be equivalent.

void RunTests() {
 WCHAR temp_path[MAX_PATH];

 GetTempPathW(MAX_PATH, temp_path);
 SetCurrentDirectoryW(temp_path);

 Test(L"abc");
 Test(L":xyz");
}


Finally we’ve have RunTests which will contains a couple of calls to Test. The function first changes the current directory to the user’s temp directory so we know we’re in a writable location and then runs Test twice with different filenames abc and :xyz. What would we expect the results to be? The first test tries to create the file abc. Nothing too strange, according to the general Win32 path conversion rules you’d expect the abc file to be created inside the temp directory. The second test :xyz is a bit more tricky, it looks like a Alternate Data Stream (ADS) name, however to be a valid stream name you need the name of the file before the colon otherwise what file would it add the stream to? Let’s find out the results by running the code:

Success (base) - abc
Success (full) - abc
Success (base) - :xyz
Error (full) - :xyz

We’ll that result is unexpected. While we guessed correctly that abc would succeed, it seems :xyz succeeded when we passed it the base filename but failed when we used the full filename. There must be some a good reason for that behavior. Let’s use a debugger to try and work out why this occurs. First I run the application in WinDBG adding the following breakpoint which will break on NtCreateFile and dump the OBJECT_ATTRIBUTES which contains the filename, the wait for the call to complete and print the NTSTATUS result:

bp ntdll!NtCreateFile "!obja @r8; gu; !error @rax; gh"

With the breakpoint set the tests can be executed, the following is the output:

Obja +00000009ac6ff828 at 00000009ac6ff828:
Name is abc
OBJ_CASE_INSENSITIVE
Error code: (Win32) 0 (0) - The operation completed successfully.
Obja +00000009ac6ff828 at 00000009ac6ff828:
Name is \??\C:\Users\user\AppData\Local\Temp\abc
OBJ_CASE_INSENSITIVE
Error code: (Win32) 0 (0) - The operation completed successfully.
Obja +00000009ac6ff828 at 00000009ac6ff828:
Name is :xyz
OBJ_CASE_INSENSITIVE
Error code: (Win32) 0 (0) - The operation completed successfully.
Obja +00000009ac6ff828 at 00000009ac6ff828:
Name is \??\C:\Users\user\AppData\Local\Temp\:xyz
OBJ_CASE_INSENSITIVE
Error code: (NTSTATUS) 0xc0000033 (3221225523) - Object Name invalid.

This at least explains why the second call to OpenFile with :xyz fails. Our call to GetFullPathName has resulted in a full path which is invalid. As I mentioned earlier you need a filename before the stream separator for the NTFS filename to be valid. But that doesn’t them explain why the first call did succeed.

The solution is CreateFile doesn’t resolve the relative path to a full path, but instead passes the same name we passed in, i.e. :xyz. This behavior's possible because the OBJECT_ATTRIBUTES structure has a RootDirectory field which contains a handle from where the kernel can start a parsing operation. Sadly !obja doesn’t print the handle value for us, so we’ll need to do it manually, replace the !obja part in the previous breakpoint to the following:

.printf \"Name: %msu Handle: %x\\n\", poi(@r8+10), poi(@r8+8)

If you change the breakpoint and re-run the application you'll get the following output:

Name: abc Handle: 94
Error code: (Win32) 0 (0) - The operation completed successfully.
Name: \??\C:\Users\user\AppData\Local\Temp\abc Handle: 0
Error code: (Win32) 0 (0) - The operation completed successfully.
Name: :xyz Handle: 94
Error code: (Win32) 0 (0) - The operation completed successfully.
Name: \??\C:\Users\user\AppData\Local\Temp\:xyz Handle: 0
Error code: (NTSTATUS) 0xc0000033 (3221225523) - Object Name invalid.

When the full path is being passed the handle is NULL, but when the relative path is used it’s the value 0x94. And what is handle 0x94? It’s a handle to the current directory, which in this case is the temp directory. So in theory we should find a named stream xyz on the temp directory if our theory is correct.


Let’s just check everything works as we expect, and let’s try it with a file as well:


So it works both for directories and files. The reason it works with CreateFile is we know the temp folder is writable, so we can create named streams. Parsing from an existing File object with a filename which starts with colon results in the NTFS filesystem accessing a named stream rather than a new file or subdirectory. It makes some kind of twisted sense, but the fact that a relative path can have a totally different behavior to a fully qualified path it clearly nor a designed in feature but an interaction between the way NTFS handles relative paths and how Win32 optimizes file access in the current directory.

I did find documentation for this behavior on MSDN, but I can no longer seem to find the page. It’s not on the obvious pages, and during searching you find archaic gems such as this. However, as I said I can’t find of a good use case for this behavior. If a privileged service is not canonicalizing and verifying paths then that’s already a potential security issue. And these paths have limited use, for example passing it to LoadLibrary fails as the path is canonicalized first and then opened.

Still, don't discount this misfeature as pointless. Never underestimate the value of unusual or undefined behavior in a system when looking for security vulnerabilities. I tend to collect, and document stupid things like this because you really never know when they might come in handy. An OS like Windows is so complex I'm always learning new things and behaviors, even before new features are added. Improving your knowledge of a system is one of the best ways to becoming an effective security researcher so don't be afraid to just mess around and test things. Even if you don't find a vulnerability you might at least get a new, interesting insight into how your platform of choice works.