FAQ: How do I start a program as the desktop user from an elevated app?

Common Vista/Win7 scenario:  the app you’ve written runs with elevated permissions, but then needs to start another program as the non-elevated desktop user.  For example, you want to display web content.  Now, you could just launch the web browser from your app, and let the web browser run as admin.  What could go wrong?  (Hint:  the correct answer will include the word “catastrophic”)

A very common mistake that programmers make is to grab a copy of the elevated, High Integrity Level access token from the current process and then “dumb it down”.  I.e., disable powerful group memberships, remove powerful privileges, and change the integrity level to Medium.  They then launch the new process with that “dumbed down” token.  This breaks for a number of reasons.

The new “LUA bug” of Vista/Win7

First and foremost, that approach makes the invalid assumption that the elevated app is running under the same user identity as the desktop user who originally logged on.  This is the new “LUA bug” of Vista and Win7.  (Refresher:  “LUA” = “limited user account”; “LUA bug” = failure that occurs when running as LUA and not administrator.  #1 cause of LUA bugs:  assumption that the end user will be an administrator.)  In Vista/Win7, everything runs as “LUA” by default, unless you specifically allow something to run elevated.  If you’re a member of the Administrators group, by default this involves a simple “consent” prompt.  The resulting app still runs as you, but with full admin rights.  If you’re not a member of Administrators, the elevation prompt requires the credentials of another account that is a member of Administrators.  The resulting app then runs as a different user.  A number of apps fail to take this second scenario into consideration.  “Dumbing down” the current process token is one example of that kind of failure.  The new program runs with reduced permissions, but not as the intended user.

There are at least a couple of other failures in that approach too, that are more obscure.  Let’s say you are a member of Administrators.  When you log on, the Windows LSA (Local Security Authority) generates two access tokens in two separate LSA-managed logon sessions.  One is the fully privileged, full-admin token; the other is the standard-user version, which is marked as linked to the full-admin token.  When you create a “dumbed-down” copy of the elevated one, the new token is still associated with the elevated session, and marked as being the “high half” of a split token.  As a result, if you start Internet Explorer with that token, Protected Mode will be disabled.  Also, if your “dumbed-down” process tries to launch an elevated app, it will simply launch the new process with the “dumbed-down” token, since it’s already marked as “elevated.”

“Enough nerditude.  Tell me what I need to do.”

So here’s one sequence that works well:

  1. Enable the SeIncreaseQuotaPrivilege in your current token (sample)
  2. Get an HWND representing the desktop shell (GetShellWindow)
  3. Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
  4. Open that process (OpenProcess)
  5. Get the access token from that process (OpenProcessToken)
  6. Make a primary token with that token (DuplicateTokenEx)
  7. Start the new process with that primary token (CreateProcessWithTokenW)

I’ve attached an example C++ project, built with VS2008 and the MFC AppWizard, and tested with x86 and x64 builds.  The meat of the sample is in RunAsDesktopUser_Implementation.cpp.  I’m sure it can be done in managed code, but that will be someone else’s project, not mine.

Caveats

Please note that there are a bunch of caveats about this approach:

  • This runs the new program in the same context as the desktop shell.  If the desktop shell process is not running (crashed or intentionally terminated), GetShellWindow fails, and there is no process token to do anything with.  Also, GetShellWindow fails if the default shell (Explorer) has been replaced with a custom shell.
  • If you have terminated the desktop shell and restarted it elevated (strongly discouraged), then the new process will also run elevated – as will pretty much everything else you start.
  • This code assumes that it is running already elevated.  If you’re not running elevated, then there is no need for this code.  If you’re not running as admin, then the necessary step of enabling SeIncreaseQuotaPrivilege won’t work anyway.
  • CreateProcessWithTokenW requires Vista or newer.  So:  this approach won’t work on pre-Vista (e.g., XP with runas); and if you want to incorporate this code in a program that can run on XP/2003, you need to use LoadLibrary/GetProcAddress to get the CreateProcessWithTokenW entry point.

RunAsDesktopUser.zip

Comments

  • Anonymous
    June 06, 2009
    What about ShellExecute?  Won't that run as the shell's user? [Aaron Margosis]  Not normally, no.  The immediate process that gets launched will run with your elevated token.  If it is a single instance program (like Explorer) that is already running, then you'll usually get something running in that initial process, but other than that, no.

  • Anonymous
    June 06, 2009
    The comment has been removed

  • Anonymous
    June 07, 2009
    Why MS doesn't provide a nw API that returns the token that was used when launching the program that causes the evelation prompt?

  • Anonymous
    June 08, 2009
    #1 requires a real Terminal Server, I believe. I don't think it's supported on client versions. Though it's possible similar behaviours exist when using the Desktop Sharing APIs, so tools like Meeting Space or Remote Assistance may break too. #2 I've mostly seen that sort of complex setup when dealing with domains where security was a big issue that were XP oriented, so that multiple user accounts were used to avoid running with a privileged token at all times. Obviously, to some extent, UAC helps to make that sort of thing unnecessary. 100% safe was perhaps badly worded, it's the only way to ensure what is the usual intent in such scenarios, i.e. to launch in the context of whomever started the application. Had it been an elevated context in the first place, it wouldn't have gone through UAC to change identity again, so that's not really an issue. Ultimately if the end-user has gone out of there way to launch your app such that everything it does is elevated, you ought to let them do so, they might know something you don't. As to apps like Process Explorer, they're mostly an edge case too, this sort of question mostly comes up around application installers and for them the bootstrap model is always the best way to go. [Aaron Margosis]  It sounds to me that you're extrapolating your own experiences to a much broader universe that you can authoritatively speak on behalf of.  Your "usual intent" may be minority.  I'd disagree that users always understand all the implications of their actions, or that any model is "always" the best one for all scenarios.  I'll reiterate the browser example, which I doubt is uncommon -- many app installers display a readme using the default web browser, and other elevated apps may also display HTML that way.  Since the bootstrapper cannot guarantee that it will always be launched as the desktop user -- not only as a non-admin, but as the desktop user -- that scenario breaks pretty badly. That said, I don't really think the bootstrap model is that much more complicated, especially if you use COM elevation rather than spawning an entirely different executable to create an elevated context. And "it's more effort to do right" is a poor excuse when it comes to security, don't you think? [Aaron Margosis]  Doing COM elevation correctly without exposing trivially exploitable EoP is not simple.  Those communication paths have to be carefully threat-modeled and implemented.  And "It's more effort to do right" does not reflect what I said at all.  If it's the best approach, the extra effort is often worth it.  Shoehorning that model where it doesn't belong only adds effort and increases risk without providing value.

  • Anonymous
    July 15, 2009
    The comment has been removed

  • Anonymous
    July 15, 2009
    Still on 2005, so I haven't seen the difference in manifests yet. As for your other statement, sorry, hadn't started working with it, just took your previous statement at face value:).

[Added moments later]  There actually is a bug in here -- the code that enables SeIncreaseQuotaPrivilege should not do so on the process token -- it should do so in the context of the thread and then revert:  ImpersonateSelf, OpenThreadToken, manipulate token, perform operations, RevertToSelf.  Fixing that is left as an exercise for the reader :)

  • Anonymous
    July 16, 2009
    Alternatively you can use the IShellDispatch2 interface: http://brandonlive.com/2008/04/27/getting-the-shell-to-run-an-application-for-you-part-2-how/ It still needs the Explorer shell to be running, but if you're willing to live with that then this approach seems much easier. And you don't even need to special case Vista/Win7 as the code should work on XP as well.

  • Anonymous
    August 27, 2009
    The comment has been removed

  • Anonymous
    November 10, 2009
    This is great material, thanks for posting. The problem I have, this method works for me for running an exe with a manifest that does not require elevation. But it appears that if the manifest is set to require elevation, then CreateProcessWithTokenW returns 740 (elevation required) even though I'm trying to run it non-elevated. I've tried a number of variations. I was previously using CreateProcessAsUser to lower the integrity, which works fine, except the process still runs as the admin. I'm about to try the IShellDesktop2 interface to see if it might work any better. [Aaron Margosis]  The only way I know of to override the "requireAdministrator" manifest is to apply the RunAsInvoker application compatibility shim.

  • Anonymous
    November 11, 2009
    The comment has been removed

  • Anonymous
    November 11, 2009
    ha! I can do this much more simply with impersonation.

  • Anonymous
    November 12, 2009
    Hi, I have a setup application which will be launched from a device driver co-installer dll. My requirement is the setup application has to launch an URL in protected mode. I followed the article given in http://msdn.microsoft.com/en-us/library/bb250462%28VS.85%29.aspx. But it doesn't help much. I also downloaded the sample application given in this article and tried. But IE is still launching with protected mode off. As driver installation happens using admin privileges the setup application is launching with admin privileges and IE also launching with admin privileges hence protected mode is off. Is there a way how we can launch IE with Protected mode ON in this situation? Help!. Thanks & Regards Suresh [Aaron Margosis]  Why does it matter whether Protected Mode is on or not?  Note that PM will be off for the Trusted Sites and Local Computer zones, as well as for the Intranet zone (for IE8).

  • Anonymous
    January 01, 2013
    here is an idea for doing so: mdb-blog.blogspot.com/.../nsis-lunch-program-as-user-from-uac.html

  • Anonymous
    March 19, 2013
    Why SE_INCREASE_QUOTA_NAME instead of SE_IMPERSONATE_NAME? CreateProcessWithTokenW requires SE_IMPERSONATE_NAME. It's CreateProcessAsUser which requires SE_INCREASE_QUOTA_NAME. [Aaron Margosis] According to the testing I did back in May 2009, that's a documentation bug.  CreateProcessWithTokenW needs SeIncreaseQuotaPrivilege.  (According to my old emails, CreateProcessWithTokenW didn't used to document requiring any privileges.  They might have fixed it incorrectly. :-/ )

  • Anonymous
    March 19, 2013
    Is there any way to get the corresponding non elevated token for the current elevated process (if any)? I mean - because Windows creates 2 tokens for an admin user at the logon time. It would be good for MS to add this. We will not have to deal with shell desktop then. [Aaron Margosis]  Unless you're writing code for your own use, it's not safe to assume that your elevated process' token is for the same user account as the non-admin desktop user (a "protected administrator" account).  But if you want to experiment (please don't ship products with this dependency), take a look at GetTokenInformation with TokenLinkedToken as the second parameter.

  • Anonymous
    December 30, 2013
    Today I learned you can also simply ShellExecute("explorer.exe", "C:pathyourapptolaunch.exe") from your admin process to run the app non-elevated. twitter.com/.../417742609646223360