I was called to task because in Writing Secure Code for Windows Vista, I asserted that from the standpoint of a service, the impersonation privilege isn't dangerous. SeImpersonate is one of the newer privileges in Windows, and has only been put there since Windows 2003 (and is in XP SP2). Basically, the attack it is trying to prevent is one where you can get a higher-level process to connect to some resource you have, impersonate them, and escalate privilege. This can happen in a number of ways:
- The higher-level process might be tricked into opening some arbitrary file, and while unlike UNIX, not everything is a file, a fair number of things can be opened with CreateFile - for example, \\.\pipe\mypipe is a fairly sure attack. In Office, we have a whole lot of APIs wrapped to do the right thing, so CreateFile becomes MsoCreateFile, which correctly sets the security quality of service bits to prevent impersonation.
- COM sources and sinks can be a nasty security hole - in this scenario, the server becomes a client when it executes the callback. If the server is running as local system and hasn't set the security settings correctly, then the client becomes localsystem, and away we go. Since COM is really only a wrapper over RPC for the most part, the same issues also apply to RPC.
- It's now possible to impersonate socket clients, so the same sort of things apply.
So it's starting to sound as if impersonation was enemy #2 (just behind buffer overruns) - why would I say it's benign? Here's the deal - by default, local system, local service, and network service are allowed to impersonate callers. To get to be running as any of these user accounts, you've got to have a service installed by an admin (or a hideously misconfigured service ACL), and if you're being attacked by the admin, just give up - it's game over. The second way you get to be one of these services, is by finding some sort of ghastly flaw in the service, taking it over, and causing it to do your bidding. Sounds like a plausible attack, so let's think about how that might work. If you've manged to own up a local system service, game over, and you win. Next, work on taking over the rest of the network (which you of course have permission from the owner to conduct pen testing on, or you risk serious consequences, like a new, unfriendly roommate in a very bad, long-stay facility). So let's say you've managed to take over a service running as the local service account - surely you can trick something running as local system to connect to you, and do rotten things. If you can, then there's a flaw in the local system service - the local system service should NEVER assume that any less privileged process is doing anything other than running malicious code. Whether the service running as local service has SeImpersonate or not, if the higher level process trusts them, they're making a mistake.
But what if an unwary admin connects to a service? Sorry - same rules apply. If you're running as admin, you're equivalent to local system (since you can install new drivers and services). If you put trust in anything running with less privileges that's on the system, it's your mistake - or the person writing the software has made a mistake.
The point here is that the service that has SeImpersonate isn't what is at risk here. The users connecting to the service are at risk, if they allow themselves to be fully impersonated.
Now let's look at the flip side - if a service ought to be impersonating, and is not, then we have an opportunity for what's called a luring attack. In a luring attack, one user tricks another into doing something on their behalf that ought not happen. Impersonation prevents luring attacks, because the action is being performed as the calling user. If you're doing something on behalf of another user, _not_ impersonating is very likely to be a problem. We can't say that impersonation is really just scary and ought not be used - then we'd have luring attacks all over the place.
Impersonation is a complex issue, and using it really correctly is something that ought to be covered much more thoroughly than what we did in either WSC2 or WSCV. Here's the bottom line:
- If you're writing a high level service, or a tool typically used by admins, understand how impersonation could be mis-used to attack your user or app. Take proper measures to only allow identification.
- If you're writing a lower level service (in terms of privilege - I don't mean drivers), you're probably doing something on behalf of other users. If you are, you may need to maintain an audit trail, and you do want to avoid luring attacks. If so, impersonation is probably a good idea.
- If you decide your service doesn't need to impersonate anything, drop the privilege. Using least privilege never hurt anything. Even if it is just a speed-bump, and an attacker with full knowledge of everything on the system could hop over to some other process, and gain the privilege, the more complex the attack, the less likely it is to work. And sometimes the attacker doesn't know everything and will trip the alarm system.
- If you do use impersonation, remember that you need to maintain correct seperation between the various users. Even hopping from one user to another can be an EoP in effect - you and your boss are probably both plain users with respect to the domain controller, but if you can run code as your boss, all sorts of interesting possibilities spring to mind (including getting sacked when caught). The thing to think about here is something called "least common mechanism" - one of the Saltzer and Schroeder design principles, which basically says that the fewer resources two users have to share, the less chances for one to usurp the privileges of the other.
The whole topic is fairly complicated, so I might spend some time on it in the future. Another wrinkle worth a future post is how you manage when you can't impersonate, but you can identify the caller. There's a bunch of relatively new functionality that makes this problem a lot easier.