Practical Windows Sandboxing – Part 3

The third tool we need in order to create a sandboxed app is a desktop. We've said in many places that the desktop is a security boundary. Unfortunately, there's little real security within a desktop – and this isn't something unique to Windows – the X Window System has some of the same class of issues, though the details vary quite a bit. Prior to the 'discovery' of what's come to be known as "Shatter" attacks, people have recognized problems inherent in having windows on the same desktop hosted by apps with different privilege levels. In fact, KB article 115825, dated from 1994, calls out this issue. I find the fact that window handles are not securable kernel objects a very unfortunate omission back when Windows NT first shipped.

If we read the documentation for CreateRestrictedToken, it tells us to place our new application on another desktop in order to prevent attacks around window messages. If you look at some of the APIs available, we find that PostMessage won't work across desktops, and neither does PostThreadMessage (fixed because of a bug I filed a few years ago). The job object can be used to prevent SwitchDesktop, which actually only keeps something from switching the desktop you see to that app's desktop, so that's not quite as useful as it first seems. So you create a new desktop, pass the name of the desktop on the lpDesktop parameter of the STARTUP_INFO struct for CreateProcessAsUser, and now we ought to be secure, right?

If only this were so – that's what I'd thought, but we've got some amazingly good security testers in the Office TWC Security test team, led by Tom Gallagher. In particular, there's one I call "evil tester" who sorted out an especially nasty attack. As it turns out, SetThreadDesktop can be used for a thread to switch to any desktop that the process can obtain a handle for, with the only restriction that the desktop has to be in the current window station for the process. Just making another window station won't help, either – the process could call SetProcessWindowStation. It would seem at first glance that the bits in the job object that control desktops ought to prevent this, but currently that's not the case. So how can a desktop be a security boundary if there's no way to prevent a process from coming back? And once it does come back, SetWindowsHook means that the restricted process has just become very non-restricted.

Initially, I thought I was completely foiled. My first thought was that if SetThreadDesktop needs a handle, maybe the handle requires some sort of permission that I can deny. No such luck. Any handle will do, no matter how few permissions are available. Next, what's the ACL on the desktop and window station? This was even more annoying – the ACL is:

Logon ID SID:F

This is just ridiculous – as it turns out, an app really only needs about 3 bits of the access mask to make a window on a desktop and run, so granting Restricted:F is excess privilege. What's even worse is that I (or someone in the security group) should have caught this in the original Windows security push. To make things even more annoying, you can't disable the logon ID SID and have a viable application, and you _have_ to have Restricted in the SidsToRestrict. Now what? "Desktop is a security boundary" – yeah, right. SetThreadDesktop, SetWindowsHook, badda-bing, crash, thud. Feh. Under normal conditions, it isn't a huge hole until someone tries to make a sandboxed app.

John Lambert of SWI came to the rescue with a particularly interesting trick. John knows ACLs about as well as anyone here, and came up with a brilliant solution. You may recall that I pointed out that you can put any valid SID in SidsToRestrict, even one that doesn't actually map to any real user or group. Now we make up a completely random SID and add it to our list of SidsToRestrict. You then grab the ACL for the current desktop that we don't want the restricted app to get back onto, and insert a deny:F ACE for the SID we just made up. The restricted process now can't get a handle at all, no other processes are affected, and without a handle SetThreadDesktop fails.

The moral of the story is that excepting dirty tricks like John thought up, the desktop isn't truly a security boundary – what's the boundary is the window station, and to be precise, it's the logon ID. If a process has a logon ID that isn't associated with any other processes, you create a window station and desktop to stick that process on, then it's completely locked down with respect to window message type attacks. Under normal conditions, there's one window station per logon ID, and one desktop per window station, so to be charitable, it's typically true that the desktop is a security boundary, and if you do something silly to put a highly privileged window on a user's desktop, you are creating security holes. However, if we're dealing with a highly restricted application, it isn't enough to just place it on a different desktop – you have to keep it from coming back.

With Vista, there's also some level of restrictions on window messages between integrity levels, and that may allow you to run a restricted app on the same desktop without too many opportunities for mayhem – SetWindowHook is blocked, along with a bunch of other things.

This concludes the long version of the talk I'll be giving Thursday – as you can see, there's no way all this will fit in 20 minutes of turbo talk.

Comments (5)
  1. Skywing says:

    Note that you can specify a more restrictive SD via CreateDesktop, or SetKernelObjectSecurity.  Runas used to use the latter amount prior to Windows XP, after which it started just reusing the caller’s logon SID instead of modifying the DACL on the desktop/winsta to grant access to a new logon SID.

    [dcl] Yes, you can, which is how the deny ACE gets added.

  2. The desktop can be a security boundary if you’re going in the other direction.  Interactive window stations typically have two desktops – winlogon and default – and stuff on the default desktop will have no access to the winlogon desktop unless they elevate to System.  

    So (thinking out loud and not all the way through) you might be able to protect ELEVATED processes from those on the default desktop by creating a new desktop with an Admin+System-only security descriptor and running the elevated processes there.  If so, this ought to work on Windows 2000 and up, and even be stronger than MIC+UIPI on Vista, at the cost of giving up (or at least complicating) interactivity.  (OTOH, this is probably moot because there remains the problem of creating the elevated process and its protected desktop from an untrusted desktop in the first place.  Never mind:-)

    [dcl] That wouldn’t be a problem if the desktop were created at logon time and left dormant, or if some system service created it for you on demand, and that’s what kept the handle. I suspect that this must be very similar to how the elevation prompt functions. Interesting idea.

  3. The elevation prompt displays on the existing winlogon desktop of the current TS session.

  4. cornedpig says:

    Why is it necessary to create a new desktop (and do the tricks you described) – when Jobs can already control access to handles with the JOB_OBJECT_UILIMIT_HANDLES restriction?

    Shouldn’t dropping all rights through a restricted token + restricting everything through Job APIs make you safe to sandbox a piece of software/malware on the main user desktop? (granted it would have very limited functionality if everything is restricted).

    [dcl] No, because it does not restrict things like calling SetWindowsHook, as well as a slew of windows message attacks (often called shatter attacks).

  5. Jednym z powodów wprowadzenia nowych formatów plików w pakiecie Microsoft Office była "zbytnia złożoność" dotychczas używanych formatów binarnych. Zaletą "starych" formatów była niewątpliwie ich szybkość, jednak wraz ze zwiększeniem się

Comments are closed.

Skip to main content