Fun With Impersonation

justin December 19th, 2006

Here’s our first "Spot the Vuln" challenge. I originally put this together for a post to Matasano’s blog, but work got pretty hectic and I had to let it slip for a bit. Now I finally have a little breathing room, so I thought this would be a good place to post it.

The below function is a thread spawned from a named pipe server in Windows. The io parameter is an open named pipe handle returned from a call to ConnectNamedPipe(); data has been read from the pipe, so impersonation shouldn’t fail.

int tclient(HANDLE io) {
     int hr = 0;
     STARTUPINFO si;
     PROCESS_INFORMATION pi;

HANDLE hProc = GetCurrentProcess();
if(!ImpersonateNamedPipeClient(io)) return GetLastError();
ZeroMemory(&si, sizeof(si)); si.dwFlags = STARTF_USESTDHANDLES; si.cb = sizeof(si); DuplicateHandle(hProc, io, hProc, &si.hStdInput, GENERIC_READ, TRUE, 0); DuplicateHandle(hProc, io, hProc, &si.hStdOutput, GENERIC_WRITE, TRUE, 0); DuplicateHandle(hProc, io, hProc, &si.hStdError, GENERIC_WRITE, TRUE, 0); CloseHandle(io);
CreateProcess(NULL, SHELL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
CloseHandle(si.hStdInput); CloseHandle(si.hStdOutput); CloseHandle(si.hStdError);
hr = RevertToSelf();
if (pi.hProcess != NULL) WaitForSingleObject(pi.hProcess, INFINITE);
return hr; }

This post is open for comments, but we will be moderating first because we don’t want to spoil the fun for everyone.

11 Responses to “Fun With Impersonation”

  1. justinon 24 Dec 2006 at 3:46 pm

    As someone new to the whole blogging experience, it didn’t quite occur to me that one should have readers before posting something requiring reader feedback. Oh well, live and learn.

    Anyway, we’re going to leave this post up for another week or so and see if anyone feels like tackling it. If no one gets it, we may just repost it again after the site’s had an opportunity to build up some readership.

  2. MarcheFunebreon 29 Dec 2006 at 3:44 am

    1) The function inconsistently returns either the result of GetLastError() which is DWORD or the result of RevertToSelf() which is a BOOL.

    2) If CreateProcess() call fails, the pi structure is left uninitialized so pi.hProcess member can take an arbitrary value (!= NULL). Calling WaitForSingleObject on that handle will most likely fail but it can also block indefinitely if the handle happens to be valid (event, mutex, semaphore, process, thread handle opened with SYNCHRONIZE access) but never signaled.

  3. olleBon 29 Dec 2006 at 2:12 pm

    Didn’t you just answer this question in the “HANDLE with care” post?
    If the DuplicateHandle() call is going to work, the impersonated user has to have PROCESS_DUP_HANDLE permission for the NP server process, which means that the process spawned by CreateProcess() could gain full access to the NP server process.
    I imagine the fix would be to DuplicateHandle() first and THEN ImpersonateNamedPipeClient().

    Happy New Year!

    /olle

  4. justinon 30 Dec 2006 at 5:20 am

    MarcheFunebre - You make legitimate points on both 1 and 2.
    1) The return values are inconsistent, which certainly is bad form, though not necessarily exploitable in this context.
    2) CreateProcess() could fail leaving pi containing potentially malicious stack junk. However, the hang will only affect the client’s thread and not the server that spawns them.
    There’s a much more serious issue, though it’s fairly subtle. At least, it was to me because I read this code repeatedly before I eventually noticed it.

    olleB - The tclient() function is a thread running in the named pipe server. So, io is passed as an open handle to DuplicateHandle(), and it will succeed without issue (barring lack of resources). However, the calls to DuplicateHandle() are at the root of the problem.

    As further encouragement, I’d like to drop a hint. Consider what could happen when this function is called in a race between two different user contexts.

  5. olleBon 30 Dec 2006 at 2:34 pm

    OK, going back to MSDN I think I may have found it.

    The docs for ImpersonateNamedPipeClient() say it “changes the thread of the calling process to start impersonating the security context of the [i]last message read from the pipe[/i]”.

    So if this thread was spawned by a low-priv users’ message and then inbetween the CreateThread() and ImpersonateNamedPipeClient() calls a high-priv user sends a message to the same pipe, the low-priv users command would run as the high-priv user.

    /olle

  6. justinon 31 Dec 2006 at 5:17 am

    olleB - Interesting idea, but the security context is set on connection to the pipe, and persists until the pipe is disconnected. Your end-goal is headed in the right direction though.

  7. Dave Aitelon 01 Jan 2007 at 10:46 pm

    Hmm. CreateProcess takes the primary thread token, if I recall correctly, and not the impersonation token. If I am not a bit out of practice (always possible) then the new process will have to be something that the impersonated user can read, but will end up with a primary token duplicated from the primary token of the parent process, which is probably not what they wanted here.

    Assuming the primary token is low priviledged - If all the inputs and outputs are inheritable, can’t my child process look at stdin- for previous handles?

    -dave

  8. justinon 02 Jan 2007 at 2:14 am

    Well, it seems like the fun’s all over. Not only did Dave catch the race condition that I was shooting for, but he caught the problem I introduced when I cut out the supporting code. The point of this example was to demonstrate that a race between two connections could result in one process inheriting its own standard IO handles, plus those for another user context connecting at the same time. Dave also pointed out that this minimized code example would not launch the child process in the impersonated thread’s context. I have to claim the blame there. I obfuscated an example from the book (page 634) and pulled out a wrapper function that would have performed the necessary token duplication and CreateProcessAsUser() call.

  9. Dave Aitelon 05 Jan 2007 at 1:59 am

    I didn’t steal the answer from the book…I only read enough of it so far to laud it publicly :>

  10. jmon 05 Jan 2007 at 2:32 am

    Ack! Our bad for some ambiguous wording there. The idea of you stealing the answer from the book never occurred to us, as it’s absurd. I think you’ve demonstrated on numerous occasions that you know this stuff pretty damn well. :>

  11. justinon 05 Jan 2007 at 5:12 am

    Hey, I’m on vacation this week and should not be held accountable for anything I do or say. Anyway, I suppose I should learn my lesson about posting in a hurry. I was referencing the example from the book just to point out that I presented it correctly at some point. At this rate though, I may need to start forwarding all of my public correspondence through a team of pre-publication reviewers.

Permanent Link | Trackback URI | Comments RSS

Leave a Reply