Code for this article: Aug99SecurityBriefs.exe (55KB)
Keith Brown works at DevelopMentor, developing the COM and Windows NT Security curriculum. He is coauthor of Effective COM (Addison-Wesley, 1998), and is writing a developer's guide to distributed security. Reach Keith at http://www.develop.com/kbrown.
I'd like to spend some time looking at
Windows NT® privileges from a programmer's perspective. Privileges are a subset of what a system administrator knows as user rights, and often can be confusing to programmers; their four-word (on average) friendly names (that you see in User Manager or the Group Policy Snap-in in Windows® 2000) usually don't say much.
Just to make a point, I looked at the first privilege listed in the online Windows NT 4.0 Resource Kit, which is: "Act as part of the operating system." The description provided for this privilege is: "A process to perform as a secure, trusted part of the operating system. Some subsystems are granted this right." Ignoring the typo in the first sentence (I'd guess that the author intended the sentence to begin "Allows a process to perform…"), what does this mean to a developer? Some subsystems are granted this right. Should my subsystem be granted this right? I'm confused!
I remember when I first tried to track down exactly what this particular privilege meant, back when I was researching Windows NT security for the very first time. I searched and searched in the MSDN documentation for the programmatic privilege that represented this user rightand was utterly frustrated until I happened to look at the documentation for LogonUser, which mentions a privilege known as SeTcbPrivilege. Somehow I was able to discover that "Tcb" stood for "trusted computing base," and I was able to make the connection (mainly by process of elimination) that SeTcbPrivilege mapped onto this user right. I was utterly humbled by this experience and realized that one of the main reasons programmers avoid security is because of all the strange terminology used by system administrators and security professionals, and lack of good documentation.
The documentation is getting better (in fact, the SeTcbPrivilege mapping is now actually listed in the Windows NT 4.0 Resource Kit in MSDN), but I've compiled a table of privileges in Figure 1 that should help make things clearer. In this table, I also show those privileges assigned (by default) to system administrators, as well as those privileges found in the System logon session. Note that I've excluded the four logon rights (log on locally and friends), as they are used a bit differently than normal privileges, and these were enumerated back in my February 1999 column.
Just for kicks, I took a look at the online help (what you get when you choose Start|Help) in Windows 2000 beta 3, and I am overjoyed to note that the documentation is much, much better than the Windows NT 4.0 Resource Kit. Check it out:
Act as part of the operating system:
This new documentation is so much more meaningful to developers, and even speaks to system administrators by describing the potential security holes that you can introduce by assigning this privilege willy-nilly. It also suggests that new applications should avoid requiring this privilege, and instead should obtain this privilege by running in the System logon session (via a service). There's hope!
This privilege allows a process to authenticate as, and therefore gain access to the same resources as any user, by calling the LogonUser APIs to create an access token. Only low-level authentication services should require this privilege.
The potential access is not limited to what is associated with the user by default, because the calling process may request that arbitrary additional accesses be put in the access token. Of even more concern is that the calling process can build an anonymous token that can provide any and all accesses. Additionally, this token does not provide a primary identity for tracking events in the audit log.
It is recommended that processes requiring this privilege are run using the LocalSystem account, which already includes this privilege. This is preferable to using a separate user account with this privilege specially assigned. Because running as LocalSystem makes use of this privilege unnecessary, this privilege might be made obsolete in a future version of Windows.
In any case, this column is devoted to helping you understand what the various privileges mean (as many of my favorites as I can fit into this column), and how they apply to your applications. Most of what I'll relate comes from painstaking searches through MSDN, as well as my own experiments in code, and I'll focus on items that I feel have a real impact on you as a programmer.
Act as Part of the Operating System
This privilege is required to call LogonUser, but there's a bit more to it. Soon after discovering that "Act as part of the operating system" mapped to the TCB privilege, I did an Internet search to see just exactly how TCB was defined, because it sounded important. Here's what I dug up from the Federal Standard 1037C, Telecommunications, Glossary of Terms:
Trusted computing base (TCB): [The] totality of protection mechanisms within a computer system, including hardware, firmware, and software, the combination of which is responsible for enforcing a security policy. Note: The ability of a trusted computing base to enforce correctly a unified security policy depends on the correctness of the mechanisms within the trusted computing base, the protection of those mechanisms to ensure their correctness, and the correct input of parameters related to the security policy.
Once I got my head around this concept, lots of things fell into place. The TCB is a boundary that defines the portion of the system that is trusted to enforce the security policy. In a secure computer system, some piece of code has to take this responsibility, and that code must be guarded against subversion.
In Windows, device drivers are included (implicitly) in the TCB, because they can pretty much do whatever they want and are not subject to the plethora of security checks that occur when user-mode threads make various system calls via the Win32® API. Thus, when you install a device driver, you've implicitly made the decision to trust that it won't do bad things like attacking the password for your machine's local administrator account. User-mode code can also be placed in the TCB by running under a user account that has the TCB privilege, but a better way is to run in the System logon session. The System logon session has this privilege by definition, and so the normal way to run code in the TCB is to install it as a service, configured to run as LocalSystem.
Backup and Restore
At first I didn't think much of these privileges, until I realized the import of granting them to someone. As an example, say you've got an NTFS partition loaded with files, each of which you've locked down via a DACL. The DACL on each file says who is allowed to read or write each of these files. Well, grant someone the backup privilege on your machine, and that person can use that privilege to gain read access to all these NTFS files, regardless of those DACLs.
This is tremendously convenient for a system administrator because a backup operator can be granted this privilege in one place rather than having to grant read permissions to all files. It's also more secure because it's harder to extricate an existing backup operator from the system if he has been explicitly granted access in the DACLs of each file. And finally, it's also more efficient since entries in DACLs take up space on your
Looking at Figure 1, you can see that administrators are normally given these privileges by default, and as a software developer you're probably an administrator on your own machine (if you're not, I pity you). So it seems as though you should be able to test this pretty easily. Just create a text file somewhere on an NTFS partition (if you are still living in the land of FAT partitions you can't play this game, sorry), and set the DACL such that it denies you all access to the file. (Technically you can't totally deny yourself all access, since as the owner of the file you are implicitly granted READ_CONTROL and WRITE_DAC permissions, which is why this experiment is safeyou can always grant yourself full permissions when you're finished with this experiment, so you can delete this file later.) One easy way to do this is to bring up the security permissions editor via Explorer and simply remove all the entries from the access control list.
Now try opening the text file via NOTEPAD.EXE. It doesn't work, does it? If you've got the Windows NT Resource Kit installed (if you don't, shame on you; put down this magazine right now and install it), run PVIEW.EXE and examine the process token for NOTEPAD.EXE. Assuming you're an administrator on your own machine, you should see something similar to Figure 2.
Figure 2 Viewing the Process Token with PView
Note that while the process token indicates the presence of the SeBackupPrivilege, it is disabled. Privileges are normally disabled by default, and must be enabled by a process before they can be used, but PVIEW.EXE makes it easy to experiment. Try enabling the backup privilege by moving it from the Disabled list to the Enabled list and pressing OK. The process now should be able to read the file, right? Try it if you like, but even after making this change, you should still be getting an Access Denied message. It turns out that in order to open a file exercising the backup or restore privileges, you need to explicitly request that the system use these privileges to grant the access you desire (read or write permission).
Figure 3 shows a little program that you can build and run to dump the contents of a text file, using the backup privilege. Note that in the call to CreateFile I specify a special flag, FILE_FLAG_BACKUP_SEMANTICS. This is the magic that tells CreateFile to exercise a privilege in order to grant the request. If you don't have the backup privilege, or it's not enabled in your token, the call to CreateFile will work only if the file's DACL grants you the access permissions you are requesting.
The EnablePrivilege helper function attempts to enable a privilege by looking up a 64-bit identifier (called a LUID or Locally Unique Identifier) that represents the privilege, and enabling the privilege in the token. (Privileges are listed in a token via LUIDs for efficiency in space and time.) If the caller hasn't been granted the privilege at all (in other words, the privilege is not even listed in the token at all), the call to AdjustTokenPrivileges will quietly succeed, but GetLastError will return ERROR_NOT_ALL_ASSIGNED. Note that EnablePrivilege explicitly checks for this common case.
The restore privilege works similarly, except it allows write access as opposed to read access. Pretty nifty, eh?
Take Ownership of Files or Other Objects
Enabling this privilege allows you to open objects for WRITE_OWNER permissions, so that you can change the owner to your own SID (or in certain special cases, to one of the groups of which you are a member). As the owner of an object, you are implicitly granted READ_CONTROL and WRITE_DAC, so you can then change the DACL on the object to suit your fancy. While most folks think of this feature as being primarily useful to system administrators for regaining access to resources when employees leave the company, SeTakeOwnership has one particularly cool use for practicing developers.
Have you ever wondered why you sometimes have difficulty killing a hung service or COM server from the task manager? It turns out that when the COM SCM or the System SCM launches a process, it sets the DACL on that process to be very restrictive. When configured to run as a distinguished principal (as opposed to running in the System logon session), the process's DACL looks something like this:
grant all permissions to <LogonSessionSID>
grant terminate, set info, and synch to SYSTEM
This means that even if you're interactively logged on as Bob, a service configured to run as Bob won't allow you to kill it. Note that in the previous DACL, it's not Bob (the principal) who is granted all permissions; it's a particular instance of Bobspecifically, the service logon session established for Bob by the SCM.|
While this is only one example of using the SeTakeOwnership privilege, the basic pattern is the same, so I've provided a sample program called KILL2 that takes this approach (see Figure 4). The basic pattern goes like this:
It's a lot of typing, but it's a great exercise to help bolster your understanding of the Windows security architecture. Taking ownership of arbitrary objects is a mechanism that every serious Windows security developer should know how to implement.
- Enable SeTakeOwnership in your token.
- Open the process handle for WRITE_OWNER.
- Restore SeTakeOwnership in your token.
- Call GetTokenInformation to get your principal SID.
- Call SetSecurityInfo on the process handle to change the owner to be your principal SID.
- Duplicate the process handle for WRITE_DAC (as the owner, you are granted this permission implicitly).
- Adjust the DACL (via SetEntriesInAcl and SetSecurityInfo), granting yourself the permissions you need (PROCESS_TERMINATE in this case).
- Duplicate the process handle, asking for the permissions you just granted yourself (PROCESS_TERMINATE).
- Use the permissions (call TerminateProcess).
Where would you be as a developer if you weren't allowed to debug processes? Actually, this privilege is pretty powerful, as attaching a debugger to a process is incredibly intrusive, and if you attach a debugger to a process running in the TCB, well, you can pretty much do whatever you want, assuming you're willing to do a little typing.
This privilege grants the holder the right to attach a debugger to any process running on the system. Interestingly enough, this privilege is not required to debug programs running in your own logon session. (In other words, if you launched the program, you are generally allowed to debug it.) So the important effect is that without this privilege you cannot attach a debugger to processes running in other logon sessions including the System logon session, which makes it pretty difficult to debug COM servers (configured to run as something other than the Interactive User) or system services.
Enabling the SeDebugPrivilege gives you the power to open handles to any process in the system with full permissions (including PROCESS_TERMINATE). This makes sense if you think about the sorts of things that a debugger needs to be able to do to be useful. This means using SeDebugPrivilege is a heck of a lot easier than using SeTakeOwnershipPrivilege to kill hung processes:
In fact, this is exactly how KILL.EXE works, one of my favorite Platform SDK samples. You can check out the source code by going to \mssdk\samples\sdktools\tlist and looking at KILL.C and COMMON.C.
- Enable SeDebugPrivilege in your token.
- Open the process handle for PROCESS_TERMINATE.
- Restore SeDebugPrivilege in your token.
- Terminate the process.
Bypass Traverse Checking
Have you ever wondered what happens if you create a directory hierarchy on an NTFS partition and deny Bob all access to one of the folders in that hierarchy? Will Bob be able to see files in subfolders below the folder that denies him access? What if you specifically grant Bob access to a file deep below that folder? If he knows the full path to the folder, will he be granted access or denied?
The cool thing is that you can control this policy via a privilege known as SeChangeNotifyPrivilege. (Yes, that is its programmatic name. Aren't you glad you're reading this article?) If a process (running as Bob, say) has this privilege enabled in its token, then even though Bob is denied access to the directory, Bob will be able to open files and subdirectories in that directory (and in its subdirectories), as long as those individual items grant him the access permissions he's looking for.
By default, this privilege is assigned to Everyone, so all tokens on your machine normally have this privilege (and this particular privilege is enabled by default, if you've got it in your token at all). So now you know the default answer to the question: Bob will be granted access. If you prefer a tighter security model, change it! If you want to enforce two separate models (for instance, if you don't want this privilege enabled for anonymous FTP clients), then create an alias (local group) called TrustedTraversers, and grant SeChangeNotifyPrivilege to TrustedTraversers (rather than Everyone). Then add anyone who you want to have traverse privileges (perhaps exclude IUSR_MACHINE to help lock down anonymous access to your FTP server). This makes it much easier to lock down Web sites. Of course, revoking this privilege makes the system perform traverse checking (which means checking DACLs on parent directories), and this could get expensive. Do some benchmarking if you're worried about the overhead.
I've described a few of my favorite privileges and how they are typically used, and I hope the table I've provided this month will prove helpful. The beauty is that once you understand how to take control over the privileges in a token, you can have very fine-grained control over some of the more subtle details of Windows security, some of which can be incredibly useful to the practicing developer.
Have a question about security? Send it to Keith Brown at http://www.develop.com/kbrown.