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 (16)

  1. 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.

  2. AndyC says:

    And that’s *still* buggy. Off the top of my head:

    1) Doesn’t work if the app is launched directly through Terminal Services, because then the shell isn’t running.

    2) Doesn’t work in complex chains, e.g. User A launches a command prompt via runas as User B, then that launches your app and does an OTS elevation to User C. Your code then runs the unelevated code as User A, not User B as it should.

    The only 100% safe way of doing this is to use a bootstrap model. You start by launching an unelevated “bootstrap” app, that starts your elevated process which does the admin work and once it has completed, the bootstrapper launches whatever process you want, which is guaranteed to be in the correct user context.

    [Aaron Margosis]  That depends on your definition of “buggy”.  I was careful to say “run the app as the non-elevated desktop user” — if there is no non-elevated desktop, then there’s no correct answer.  The bootstrap model won’t work in that case either.

    Re #1 and terminal services:  I can’t repro this — when I try to configure the “start a program” option in mstsc, it has no effect (going from a Vista SP1 to another one).  I tried a bunch of different ways, but no luck.  The shell just comes up each time.  Can you specify an app that requires elevation?  What happens when you do?

    Re #2 and “not User B as it should.” (emphasis mine.)  First of all, this seems an odd edge case — under what non-contrived scenarios are you going to have a chain of three independent security contexts?  And why do you assert that the non-elevated app should run as User B and not as User A?  How do you know that the User B context is not elevated too?  Is that an elevated command prompt?

    The bootstrap model is another model that can be appropriate in some scenarios.  However, it is not “100% safe”.  That model builds in an assumption that the bootstrap app was started in a “safe” non-elevated context.  What if the user started the bootstrap app with elevation or from an elevated CMD?  Then everything runs elevated.

    Second, the bootstrap model is more complex.  If the bootstrap app needs to do more than simply wait for the elevated process to exit, then you need to build interprocess communication between the two processes so that the elevated app can tell the bootstrap app “now do XYZ.”  It’s not impossible, of course, but it’s much more complex than my sample.  (And of course, complexity increases risk of bugs, and those can easily be security issues in a scenario like this.)

    Third, sometimes the bootstrap model is simply not appropriate.  Say you’ve built an app that legitimately requires admin rights (my favorite example is Process Monitor).  The app has a feature or two that is rarely used but needs to launch a browser — like Procmon’s “search online” feature.  In order to use the bootstrap model, Procmon would always need to be started non-elevated, and would need to keep that non-elevated bootstrap app around as long as Procmon were running, just in case the “search online” feature got invoked.  That makes no sense to me at all.

    [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 🙂

    [One more late comment]  In your complex chain scenario, if the “non-elevated” app you want to start is IE7, it will refuse to run as User B.

  3. Bob says:

    Why MS doesn’t provide a nw API that returns the token that was used when launching the program that causes the evelation prompt?

  4. AndyC says:

    #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.

  5. Roy Folkker says:

    Having worked on installers (and bootstraps) extensively, I have to say that using the bootstrap as your non-elevated point for a split level execution is not only a bad idea, but very impractical.  In the case of my current work, my bootstrapper actually performs the execution of the MSI package through API calls.  As well, it can perform patches, and launch executables through a series of prerequisites.  Assuming I choose to have it run as invoked, or as user, that means that every prerequisite will require UAC confirmation (retrieving the token from the process will not work since MSP and MSI function calls do not allow me to elevate them), and once an MSI or MSP package hits a required elevation action, my executable will be elevated, since it is not a parent process, it is the current process, rendering all other considerations moot.

    With all of that aside, there is the ever popular issue of Microsoft key words for UAC.  If you name an executable setup, update, or some variations there-in, congratulations, you are elevated.  Again, rendering the bootstrap as a non-elevated launch point as moot.

    [Aaron Margosis]  Installer detection is only for “legacy” apps.  If the app has a Vista-aware manifest (i.e., specifies requestedElevationLevel), it overrides installer detection, so you can have an app called setup.exe that doesn’t prompt for elevation.  By default, Visual Studio 2008 adds a Vista-aware manifest to apps you build.

    So, either way, from an installer stand-point, *most* if not all situations will have a default shell.  If you are installing software via TS to a terminal server, and succeeding you deserve an award (or to be taken out back for doing something that scary).  So, the concept of creating your non-admin tool processes as user through an Admin process seem far more beneficial (especially considering in most situations you *want* to be as admin in an install process, with very rare non-admin addendums (e.g. launching an external read-me, launching a Support page, and such).

    Anyways, thank you for the information here.  Again, Mr. Margosis you have provided information that has been unbelievably useful and just incomplete enough to force me to make sure I know what I am doing:P.

    [Aaron Margosis]  Incomplete?  Even with the code sample?

  6. Roy Folkker says:

    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 🙂

  7. ilmcuts says:

    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.

  8. Boon Hong says:

    In my case, I am creating a flash application as the interface to execute some programs and installers from the disc.

    Since I need to execute the installers in elevated mode, I have to amend the default manifest in the flash application to launch in elevated mode too. But by doing this, I will cause all programs launched from flash to be in elevated mode. This is a potential security leakage.

    It will be good if Microsoft can provide a command line utility to downgrade the elevated mode that can be executed from batch files.

    It will even be better if Microsoft is willing to fix this security loophole by having all child process to be launched in non-elevated mode by default just like all applications are launched in non-elevated mode by default.

  9. Bill Zitomer says:

    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.

  10. Bill Zitomer says:

    Yeah that’s not going to work in this case because I need to distribute. What I have is a custom install process which needs to read a cookie left by our web site. The cookie resides in the low cache, which the installer cannot access. What I did is re-run the installer as a low integrity process and use InternetGetCookie, which works fine if the user is an admin, but for standard user it fails because it’s still running as admin, whereas the cookie left by IE was running as the user. I ended up making a new exe with runAsInvoker in the manifest and I think this will work. I will need to embed it, extract at run time, etc. All of this to do what used to be one line of code!

  11. Bill Zitomer says:

    ha! I can do this much more simply with impersonation.

  12. Suresh says:

    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).

  13. Alexander says:

    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. :-/ )

  14. Alexander Dyagilev says:

    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.

  15. 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