Sunday, January 20, 2008

Namedpipe Impersonation Attack


Privilege escalation through namedpipe impersonation attack was a real issue back in 2000 when a flaw in the service control manager allowed any user logged onto a machine to steal the identify of SYSTEM. We haven't heard a lot about this topic since then, is it still an issue?

First of all, let's talk about the problem.

When a process creates a namedpipe server, and a client connects to it, the server can impersonate the client. This is not really a problem, and is really useful when dealing with IPC. The problem arises when the client has more rights than the server. This scenario would create a privilege escalation. It turns out that it was pretty easy to accomplish.
For example, let's assume that we have 3 processes: server.exe, client.exe and attacker.exe. Server.exe and client.exe have more privileges than attacker.exe. Client.exe communicates with server.exe using a namedpipe. If attacker.exe manages to create the pipe server before server.exe does, then, as soon as client.exe connects to the pipe, attacker.exe can impersonate it and the game is over.

Fortunately, Microsoft implemented and recently documented some restrictions and tools to help you manage the risk.

First of all there are some flags buried in the CreateFile documentation to give control to the pipe client over what level of impersonation a server can perform. They are called the "Security Quality Of Service".

There are 4 flags to define the impersonation level allowed.

SECURITY_ANONYMOUS
The server process cannot obtain identification information about the client, and it cannot impersonate the client.


SECURITY_IDENTIFICATION
The server process can obtain information about the client, such as security identifiers and privileges, but it cannot impersonate the client. ImpersonateNamedpipeClient will succeed, but no resources can be acquired while impersonating the client. The token can be opened and the information it contains can be read.


SECURITY_IMPERSONATION - This is the default
The server process can impersonate the client's security context on its local system. The server cannot impersonate the client on remote systems.


SECURITY_DELEGATION
The server process can impersonate the client's security context on remote systems.


There are also 2 other flags:

SECURITY_CONTEXT_TRACKING
Specifies that any changes a client makes to its security context is reflected in a server that is impersonating it. If this option isn't specified, the server adopts the context of the client at the time of the impersonation and doesn't receive any changes. This option is honored only when the client and server process are on the same system.


SECURITY_EFFECTIVE_ONLY
Prevents a server from enabling or disabling a client's privilege or group while the server is impersonating.


Note: Since the MSDN documentation for these flags is really weak, I used the definition that can be found in the book "Microsoft® Windows® Internals, Fourth Edition" by Mark Russinovich and David Solomon.

Every time you create a pipe in client mode, you need to find out what the server needs to know about you and pass the right flags to CreateFile. And if you do, don't forget to also pass SECURITY_SQOS_PRESENT, otherwise the other flags will be ignored.

Unfortunately, you don't have access to the source code of all the software running on your machine. I bet there are dozen of software running on my machine right now opening pipes without using the SQOS flags. To "fix" that, Microsoft implemented some restrictions about who a server can impersonate in order to minimize the chances of being exploited.

A server can impersonate a client only if one of the following is true.
  1. The caller has the SeImpersonatePrivilege privilege.

  2. The requested impersonation level is SecurityIdentification or SecurityAnonymous.

  3. The identity of the client is the same as the server.

  4. The token of the client was created using LogonUser from inside the same logon session as the server.


Only Administrators/System/SERVICES have the SeImpersonatePrivilege privilege. If the attacker is a member of these groups, you have much bigger problems.

The requested impersonation level in our case is SecurityImpersonation, so the second point does not apply.

That leaves us with the last two conditions. Should we worry about them? I think so. Here are some examples:

I'm on XP. I want to run an untrusted application. Since I read my old posts, I know that I can run the process using a stripped down version of my token. Unfortunately, my restricted token has the same identity as the normal token. It can then try to exploit all applications running on my desktop. This is bad.

My main account is not administrator on the machine. When I want to install software, I use RunAs. This brings up a new problem. RunAs uses LogonUser, and it is called from the same logon session! That means that my untrusted application using a restricted token derived from a standard user token can now try to exploit and impersonate a process running with administrator rights! This is worse.

But how real is all this?

This is hard to say. I don't have an idea about the percentage of applications using the SQOS flags. We must not forget that allowing impersonation is also required and desired in certain cases.

For fun I took the first application using namedpipes that came to my mind: Windbg. There is an option in Windbg to do kernel debugging and if the OS you are debugging is inside vmware, you can specify the namedpipe corresponding the COM1 port of the vmware image. By default it is "com_1". My untrusted application running with the restricted token was now listening on com_1, and, easy enough, as soon as I started windbg, the untrusted application was able to steal its token.

To be fair I have to say that vmware displayed an error message telling me that the com_1 port was "not configured as expected". I should not have started windbg knowing that. But, eh, who reads error messages? :)

What should we do now?

Well, it turns out that Microsoft implemented two new restrictions in windows Vista to fix these problems. I don't think they are documented yet.

  1. If the token of a server is restricted, it can impersonate only clients also running with a restricted token.

  2. The server cannot impersonate a client running at a higher integrity level.


These new restrictions are fixing both my issues. First of all my untrusted application can't be running with a restricted token anymore. Then, even if the untrusted application is running with my standard token, it won't be able to impersonate the processes that I start with the "Run As Administrator" elevation prompt because they are running with a High Integrity Level.

Now it is time to come back to the main question: Is it still an issue?

My answer is yes. Windows XP is still the most popular Windows version out there and there is no sign that Vista is going to catch up soon. But I have to admit that I'm relieved to see the light at the end of the tunnel!

--

Some technical details:
When you call ImpersonateNamedPipeClient and none of the conditions is met, the function still succeeds, but the impersonation level of the token is SecurityIdentification.

If you want to try for yourself, you can find the code to create a server pipe and a client pipe on my code page.

Related link:
Impersonation isn't dangerous by David Leblanc

DuplicateHandle and Access Mask.

In the DuplicateHandle MSDN documentation there is not a lot of information about the Access Mask that can be passed to DuplicateHandle.

What happens if you ask for MAXIMUM_ALLOWED?

The function succeeds, but the handle created does not have any granted access. This is not really useful.

Not all object types support the same flags in an access mask, what happens if you specify a mask that contains invalid flags?

It depends...

The function fails if you use any of these flags:

0x00200000 = Unused standard right
0x00400000 = Unused standard right
0x00800000 = Unused standard right
0x01000000 = ACCESS_SYSTEM_SECURITY [You need a privilege]
0x04000000 = Reserved
0x08000000 = Reserved

But if you don't, any other invalid flags are ignored and the call succeeds. The granted access is the union of all the valid flags present in the mask. It also means that if you duplicate the handle using invalid flags only, the handle will be created without any granted access.


That said, in my previous post I talked about why you should not use PROCESS_ALL_ACCESS anymore on Windows XP. This is not true for the DuplicateHandle call, you can still use it since the new flag 0xF000 will be ignored on previous version of the OS.

Saturday, January 12, 2008

WINVER Mayhem. The PROCESS_ALL_ACCESS problem.

If you are like me, you want to develop Windows applications that work on all the major versions of Windows. At the moment it is Windows Vista, Windows XP SP2 and maybe Windows 2000 SP4. You also want to use the new features introduced in the new versions.

Suppose that you want to use a something that is defined only when you have _WIN32_WINNT=0x600. What do you do?

There are 3 options:

1. Copy the definition manually to one of your file. This is not clean. I hope I won't have to do this.

2. Create one build per OS version. Each of them will define _WIN32_WINNT accordingly and you just have to be sure that the code specific to an OS version is inside an #IF _WIN32_WINNT >= VERSION. This makes sense, but this is a lot of troubles. Who wants to have multiple sets of exes and dlls and ship different binaries depending on the user configuration.

3. Define _WIN32_WINNT to the highest value (0x0600 for Vista in this case), and make sure that you don't use unsupported functions/structures on previous version. You need to use GetVersionEx to get the OS version and use GetProcAddress to get the address of the functions that are not defined in prior versions.

The third option is the one that I've seen most of the time. Unfortunately, changing the value of _WIN32_WINNT to 0x0600 is causing some unexpected problems.

One example is the define for PROCESS_ALL_ACCESS. It looks like this:

#if (NTDDI_VERSION >= NTDDI_LONGHORN)
#define PROCESS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \
0xFFFF)
#else
#define PROCESS_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \
0xFFF)
#endif

They changed the mask on Vista to include 0xF000. 0xF000 contains 3 unused flags and the new PROCESS_QUERY_LIMITED_INFORMATION.

Why is this a problem?

Suppose that your code is doing this:


OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);

This call is now going to fail on XP and 2000 because you are trying to open a process with 0xF000, and you don't have that access on the process.

You need to fix all PROCESS_ALL_ACCESS occurences in your code. The same problem exists with THREAD_ALL_ACCESS.

What would have been a better solution? I thought PROCESS_QUERY_LIMITED_INFORMATION was only a subset of PROCESS_QUERY_INFORMATION already present in the mask. Did they really need to add it? Could they have used GENERIC_ALL instead? As we saw in this earlier post, GENERIC_ALL can mean different things on different OS versions.

Sunday, January 6, 2008

More GENERIC_MAPPING changes on vista

In my last post I talked about the change in the generic mapping of the Process type. It turns out that Thread, Token and Key also changed on Vista.

For reference, these are the changes:



TOKEN:
Windows XP Windows Vista
+-----------------+-------------------+---------------------------+
GENERIC_EXECUTE READ_CONTROL READ_CONTROL
ASSIGN_PRIMARY
IMPERSONATE
+-----------------+-------------------+---------------------------+
GENERIC_READ READ_QUERY READ_QUERY
READ_CONTROL READ_CONTROL
DUPLICATE
QUERY_SOURCE
+-----------------+-------------------+---------------------------+
GENERIC_WRITE ADJUST_PRIVILEGES ADJUST_PRIVILEGES
ADJUST_GROUPS ADJUST_GROUPS
ADJUST_DEFAULT ADJUST_DEFAULT
READ_CONTROL READ_CONTROL
ADJUST_SESSIONID
+-----------------+-------------------+---------------------------+
Note: The TOKEN_ prefix has been removed.



THREAD:
Windows XP Windows Vista
+-----------------+-------------------+---------------------------+
GENERIC_EXECUTE READ_CONTROL READ_CONTROL
SYNCHRONIZE SYNCHRONIZE
QUERY_LIMITED_INFORMATION
+-----------------+-------------------+---------------------------+
GENERIC_READ GET_CONTEXT GET_CONTEXT
QUERY_INFORMATION QUERY_INFORMATION
READ_CONTROL READ_CONTROL
+-----------------+-------------------+---------------------------+
GENERIC_WRITE TERMINATE TERMINATE
SUSPEND_RESUME SUSPEND_RESUME
SET_CONTEXT SET_CONTEXT
SET_INFORMATION SET_INFORMATION
READ_CONTROL READ_CONTROL
0x00000004 0x00000004
SET_LIMITED_INFORMATION
+-----------------+-------------------+---------------------------+
Note: The THREAD_ prefix has been removed.



KEY:
Windows XP Windows Vista
+-----------------+-------------------+---------------------------+
GENERIC_EXECUTE QUERY_VALUE QUERY_VALUE
ENUMERATE_SUB_KEYS ENUMERATE_SUB_KEYS
NOTIFY NOTIFY
READ_CONTROL READ_CONTROL
CREATE_LINK
+-----------------+-------------------+---------------------------+
GENERIC_READ QUERY_VALUE QUERY_VALUE
ENUMERATE_SUB_KEYS ENUMERATE_SUB_KEYS
NOTIFY NOTIFY
READ_CONTROL READ_CONTROL
+-----------------+-------------------+---------------------------+
GENERIC_WRITE SET_VALUE SET_VALUE
CREATE_SUB_KEY CREATE_SUB_KEY
READ_CONTROL READ_CONTROL
+-----------------+-------------------+---------------------------+
Note: The KEY_ prefix has been removed.


You want to dump the GENERIC_MAPPING structure for other object types? You can find the code on http://nsylvain.googlepages.com/.

You can terminate a higher integrity process.

Integrity levels have been publicized a lot like a "No-Write-Up" technology. It means that you can't write to objects with a higher integrity level than your token. But there is more to it. Microsoft also implemented "No-Read-Up" and "No-Execute-Up" and you can use them on your objects. It is even used today for processes, by default they have a No-Read-Up and No-Write-Up mandatory label. It would have been too much of a security hole to be able to read the process memory of a higher integrity level process.

No-Execute-Up is not present though. What does it buy us?

If it was on Windows XP, then not much, but on Vista the Generic Mapping structures have changed. Let's take a look at them side by side for the "Process" object type on XP and Vista.


Windows XP Windows Vista
+-----------------+-------------------+---------------------------+
GENERIC_EXECUTE READ_CONTROL READ_CONTROL
SYNCHRONIZE SYNCHRONIZE
TERMINATE
QUERY_LIMITED_INFORMATION
+-----------------+-------------------+---------------------------+
GENERIC_READ VM_READ VM_READ
QUERY_INFORMATION QUERY_INFORMATION
READ_CONTROL READ_CONTROL
+-----------------+-------------------+---------------------------+
GENERIC_WRITE CREATE_THREAD CREATE_THREAD
VM_OPERATION VM_OPERATION
VM_WRITE VM_WRITE
DUP_HANDLE DUP_HANDLE
CREATE_PROCESS CREATE_PROCESS
SET_QUOTA SET_QUOTA
SET_INFORMATION SET_INFORMATION
SUSPEND_RESUME SUSPEND_RESUME
READ_CONTROL READ_CONTROL
TERMINATE
+-----------------+-------------------+---------------------------+
note: the prefix PROCESS_ has been removed.
http://msdn2.microsoft.com/en-us/library/ms684880(VS.85).aspx


As you can see GENERIC_EXECUTE has changed and now gives TERMINATE access. You can then kill processes with a higher integrity level!

For completeness, PROCESS_QUERY_LIMITED_INFORMATION allows you to query the full process image name.

Saturday, January 5, 2008

The integrity drop - or - How to disable UIPI take 2

On vista if you create a process with a low integrity level token, the User Interface Privilege Isolation (UIPI) will prevent it from talking to windows owned my medium/high integrity level processes.

What if you don't want that? Maybe you already have another plan for UI protection? Running in a job for example.

There is the easy way: [From MSDN]


By specifying UIAccess=”true” in the requestedPrivileges attribute [of the application manifest], the application is stating a requirement to bypass UIPI restrictions on sending window messages across privilege levels. Windows Vista implements the following policy checks before starting an application with UIAccess privilege.
The application must have a digital signature that can be verified using a digital certificate that chains up to a trusted root in the local machine Trusted Root
Certification Authorities certificate store.
The application must be installed in a local folder application directory that is writeable only by administrators, such as the Program Files directory.


Unfortunately this is not going to work if you application can be installed by non-admin users.

Fortunately, there is another (not documented) way to "disable" UIPI.

Windows initializes UIPI during the process startup. If you create the process with a low integrity level, then UIPI will prevent it from accessing windows owned by medium/high integrity level processes.

But no one said that it was not possible to change the integrity level of a process AFTER it is started. If you own the process, and you trust that it's not going to run any potentially malicious piece of code before main(), you can add code at the beginning of your main() to drop the integrity level. From now on, the process is going to run with the new integrity level and UIPI won't be updated.

Dropping the integrity level is easy:

Get a handle to the process token using OpenProcessToken and call SetTokenInformation on it with the TokenIntegrityLevel information class to set the new integrity level.

But this is not all. If you only do this, your process won't be able to play sound. This is because when you play sound, audiodg.exe creates some objects on your behalf. At some point it will impersonate your token and open your process. Since your process was created with a medium integrity level token, the integrity/mandatory label on the process is Medium. When audiodg tries to open your process with your token, it does not have access and it fails.

What you must do is change the security label on the process to be low integrity level. The easy way to do this is to create a SDDL string for the integrity label and to follow the code in this example. You should use this SDDL string: "S:(ML;;NWNR;;;LW)" It means that it's a SDDL for a SACL (S:), with a Mandatory Label ace type (ML), the ace access is No-Write-Up and No-Read-Up (NWNR) and the integrity level is low (LW). The example is using NW instead of NWNR but for a process it's better to prevent lower privileges processes from being able to read it's process memory.

SetWindowsHookEx and Restricted Token

One easy way on Windows to inject a DLL is to use SetWindowsHookEx.

From MSDN:

The SetWindowsHookEx function installs an application-defined hook procedure
into a hook chain. You would install a hook procedure to monitor the system for
certain types of events. These events are associated either with a specific
thread or with all threads in the same desktop as the calling thread.




When the event you are interested in is triggered for the first time in a process, your DLL is going to get loaded and your hook procedure will be called. For example, if you set a mouse hook, the DLL is going to get loaded in the process as soon as you move the mouse over a window owned by this process.

But the desktop is not a perfect security boundary. You can have applications on the desktop running as a different users, with different rights. You can also have some applications running as a restricted version of yourself. How can we ensure that an application can't use SetWindowsHookEx to elevate it's privilege?

There are multiple answers.

First of all you can put this application on a job object and set the UILIMIT_HANDLES restriction. With this restriction a process in the job object can set hooks only on processes that are also in the job. But setting this flag also means that you can't access any user handles (HWND, etc) created by processes not associated with the job. If your application has some UI, then you have a lot of work to do if you want to fix everything that is going to break! (No icons, no cursors, can't access the desktop window). UserHandleGrantAccess can help you here...

On vista it's a little bit easier, you can run the application with a low integrity level token and User Interface Privilege Isolation (UIPI) will do the work for you. Internet Explorer 7 is using it to be more secure. It will prevent your application from setting hooks on other applications running with a higher integrity level.

What if you can't use any of them? Well, if your application is running with a restricted token, you may not have to worry about this at all. A restricted token is a stripped down version of your token created with the CreateRestrictedToken api. The SidsToRestrict array passed to the function must be non-null. In other words, IsTokenRestricted must return true. If this is the case, Windows will do the right thing and will not trust your application if you try to set a hook.

I haven't found any documentation on this... but during my tests I have seen two different behaviors:

  1. For some hooks (WH_MOUSE for example), when the event is received in another process, instead of loading the DLL, it sends a message to the thread that registered the hook. The message is handled internally and will cause the hook procedure to be called. Your application is now receiving and parsing all the events from all processes by itself in its own process. That does not sounds very efficient to me, but at least it's not a security flaw. (Don't quote me on this)
  2. For some other hooks (WH_CALLWNDPROC for example), the event seems to be ignored and the hook procedure is never called.

I haven't done a lot of testing so far on this, but it seems like a thread running with a restricted token can't inject a DLL using SetWindowsHookEx, which is good.

One warning: If you try to do this, there is a nasty side effect. Since the events are now sent to the calling thread using window messages, you must have a message loop, otherwise your system is going hang! I don't think there was any requirement before that the caller of SetWindowsHookEx have to peek messages.