Privilege VulnerabilitiesNow that you are familiar with the basic privilege management API, you can explore the types of mistakes developers are likely to make when attempting to perform privilege management. Reckless Use of PrivilegesThe most straightforward type of privilege vulnerability happens when a program running with elevated privileges performs a potentially dangerous action on behalf of an unprivileged user without first imposing any limitations on itself with privilege management functions. Although it is possible for programs to safely access resources without needing to temporarily or permanently drop privileges, it is very easy to make mistakes when doing so. Here is a simple real-world example of a setuid root program named XF86_SVGA that used to ship with the XFree86 windowing package. Nicolas Dubee, a notorious and gifted researcher, discovered this vulnerability in 1997. Listing 9-1 is an excerpt from his advisory (available at http://packetstormsecurity.org/advisories/plaguez/plaguez.advisory.010.xfree86). Listing 9-1. Privilege Misuse in XFree86 SVGA Server
The XF86_SVGA server, which was a setuid root program, happily read the configuration file /etc/shadow, and then proceeded to complain about the unrecognized option of root's password hash! The problem is that the X server would read in any configuration file the user requested as root, without regard for the actual user's permissions. Its configuration file parser happened to display a verbose error message, which printed the first line of the suspect configuration file. Considering the effects of any elevated group privileges is important, too. Many programs are installed as setgid so that they run as a member of a particular group. If the program performs a privileged action without relinquishing group privileges, it can still be vulnerable to a privilege escalation attack by allowing the user to access resources designated to the group in question. For example, the /sbin/dump program in NetBSD was installed as setgid tty so that it could notify system administrators if backup media needed to be changed. The dump program never dropped this group privilege, and local users could have the dump program start a program of their choice by setting the libc environment variable RCMD_CMD. This program would then run with an effective group ID of tty. Attackers could seize group tty privileges, which could allow them to interact with other user's terminals. Dropping Privileges PermanentlyOccasionally, application developers will make mistakes when writing the code for a program that permanently relinquishes its privileges. The following sample code represents part of a setuid root program: /* set up special socket */ This code is similar in spirit to what you find in several common network programs. The program needs to be root to obtain a socket bound to a port below 1024 or to obtain a special socket for sniffing. The author wants the program to be safe and follow a least-privilege design, so after obtaining this socket, the program drops its root privileges by performing a setuid(getuid()), which sets the saved set-user-ID, the real user ID, and the effective user ID to the value of the real user ID. setuid(getuid()) is a common idiom for permanently relinquishing privileges, and it usually works without too many complications. However, in some situations, it's not enough, as explained in the following sections. Dropping Group PrivilegesSome programs are installed as both setuid and setgid, meaning they run with an elevated user ID and elevated group ID. The code in the previous section would be fine if the program is only setuid root, but if the program is setuid root and setgid wheel, the elevated group privileges aren't relinquished correctly. In the processing loop, the effective group ID of the process is still set to the privileged wheel group, so if attackers found a way to exploit the program in the main processing loop, they could gain access to resources available to that privileged group. The correct way to address this problem is to relinquish group privileges like this: /* set up special socket */ This code drops the group permissions and then the user permissions. It seems fairly straightforward, but it can actually be done incorrectly, as shown in the following example: /* set up special socket */ This code doesn't fully work because the function calls are ordered incorrectly. The setuid(getuid()) function relinquishes root privileges. Remember that having an effective group ID of 0 doesn't mean you are a superuser, as superuser status is based solely on your effective user ID. The setgid(getgid()) call is performed with privileges of the nonprivileged user, so the result of the setgid(getgid()) call depends on the OS. In Linux, Solaris, and OpenBSD, only the effective group ID is modified, and the saved set-group-ID still contains the group ID of the privileged group. If attackers find a flaw in the program they could leverage to run arbitrary code, they could perform a setegid(0) or setregid(-1, 0) and recover the elevated group privileges. Dropping Supplemental Group PrivilegesPrograms running as daemons can run into security issues related to dropping privileges that are a little different from setuid programs. This is because they are typically started as a privileged user and then assume the role of an unprivileged user based on user input. In this situation, you have to be cognizant of supplemental group IDs because if they aren't updated when privileges are dropped, they could leave the process with access to privileged resources. Certain implementations of the rsync application contained a vulnerability of this nature, which is detailed at http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-0080. If rsync runs as a daemon, it starts off with the user ID and groups of the user running the daemon (typically root). If the rsync daemon needs to operate as an unprivileged user, it runs the following code: if (am_root) { This code releases the effective group ID before the effective user ID, so it should drop those privileges in the correct order. However, this code doesn't drop the supplementary group privileges! The developers solved this problem by inserting the following code: #ifdef HAVE_SETGROUPS Note that setgroups() works only if you are the superuser and have an effective user ID of 0. This is another reason it's important to relinquish privileges in the correct order. Dropping Nonsuperuser Elevated PrivilegesAs discussed earlier, the behavior of the setuid() and setgid() functions are different if the program isn't running as the superuser. setuid(getuid()) is a reasonable idiom for a program running as root that wants to drop privileges permanently, but if the effective user ID isn't 0, the same tactic yields system-dependant, and sometimes inadequate results. Say that the simple network program was changed so that instead of being setuid root and setgid wheel, it's setuid to another nonprivileged user, such as daemon. This might happen if you installed a kernel-hardening patch that let programs with a particular user ID or group ID allocate special sockets to avoid the root privilege requirement. The code would look the same: /* set up special socket */ However, the semantics of this code would be quite different when not running with an effective user ID of 0. Both setgid() and setuid() would be called as nonprivileged users, and they would change only the effective IDs, not the saved IDs. (In FreeBSD and NetBSD, this code would change all three IDs, so it wouldn't be vulnerable.) Attackers who exploited a problem in the program could therefore regain any relinquished privileges. The solution for nonsetuid root applications that need to fully drop their privileges is to use the setresgid() and setresuid() functions or the setregid() and setreuid() functions if necessary. OpenBSD versions before 2.3 require two calls to setuid(). A noted researcher named Michael Zalewski found a bug in Sendmail 8.12.0 (documented at www.sendmail.org/releases/8.12.1.html) that's a good real-world example of this situation. Sendmail used to install a set-user-ID root binary, but in version 8.12.0, it moved to a new configuration, with a set-group-ID smssp binary. Here's the code that is intended to drop the elevated group privileges: int First, setgroups() fails, but that's fine because the supplemental groups are ones for the real user, which is a nonprivileged account. setgid() successfully changes the effective group ID from the saved set-group-ID to the real group ID but doesn't fully drop the privileges (except in FreeBSD and NetBSD). The saved set-group-ID still has the privileged smssp group ID. The Sendmail developers fixed the issue by replacing the call to setgid() with conditionally compiled calls to setresgid() or setregid(), depending on which function is available. Mixing Temporary and Permanent Privilege RelinquishmentMany applications designed to run in an elevated context are programmed by security-conscious developers who adopt a model of least privilegesrunning an application with the minimal set of privileges it requires at a certain time to achieve its objectives. This model often means running as the invoking user for the bulk of the program and temporarily switching to a more powerful user when a privileged operation is required. If no more privileged operations are required, often the application permanently relinquishes its elevated user-ID by using setuid(). Although this model is preferred for developing a privileged application, subtle errors can result in using setuid() when the effective user-ID has been changed previously, as shown in this example: #define STARTPRIV seteuid(0); This code starts out by relinquishing its privileges temporarily with seteuid(realuid). When the program needs its root privileges, it uses the STARTPRIV macro to obtain them and the ENDPRIV macro to release them. Those macros work by calling seteuid(0) and seteuid(realuid), respectively. After a bit of processing, the program decides it wants to fully drop its privileges, and it does that with the common idiom setuid(realuid). The problem is that at this point, the effective user ID is the real user ID of the program, not 0. Therefore, setuid(realuid) doesn't affect the saved set-user-ID in most UNIX implementations, with FreeBSD and NetBSD being the major exceptions. If attackers find a way to co-opt the program after the final privilege drop and run a seteuid(0), they could recover root privileges from the saved set-user-ID. Here's another example: void temp_drop(void) This code represents a simple set-user-ID root application. The main loop contains two steps: option processing and main processing. The option-processing code needs root privileges, but it temporarily drops them to process a potentially unsafe option. After the option-processing code is completed, the program enters step2, the main processing section. The rest of the code is complex and potentially prone to security issues, so it fully drops privileges with a setuid(getuid()) before continuing. The problem is that if an unsafe option signals that the option processing is prematurely complete, the jump to step2 happens while privileges are temporarily dropped. The setuid(getuid()) call succeeds, but it doesn't correctly clear the saved set-user-ID in the process, except in FreeBSD and NetBSD. Therefore, if there's an exploitable problem in the main processing code, users can reclaim root privileges by performing a seteuid(0), which succeeds because the saved set-user-ID is still 0. Dropping Privileges TemporarilyTemporary dropping of privileges can also be difficult to implement correctly. Many of the pitfalls in permanent privilege relinquishment can be applied to temporary privilege changes as well. Furthermore, dropping group privileges (and supplemental group privileges) is an easy step to overlook. Finally, the order in which privileges are relinquished can cause some privileges to be retained mistakenly. Using the Wrong IdiomIf you drop privileges temporarily, your program is still vulnerable to a low-level attack, such as a buffer overflow. If attackers can run arbitrary code within the context of your process, they can issue the necessary system calls to propagate a saved set-user-ID to the effective and real user ID fields and regain privileges. To avoid this possibility, dropping privileges permanently as soon as possible is the safest option for a setuid application. Tcptraceroute had a simple permission-related problem that a security specialist from Debian Linux named Matt Zimmerman discovered. The program intended to drop privileges permanently, but the author used the idiom for dropping privileges temporarily. Here's the vulnerable code: defaults(); This mistake was a simple one: The authors used the wrong function. They should have used setuid() rather than seteuid() to prevent privileges from being reclaimed later. Any memory corruption vulnerability that occurred in the application's trace() function could allow privileges to be regained simply by using seteuid(0). The full advisory is archived at http://freshmeat.net/articles/view/893/. Dropping Group PrivilegesNow take a look at a real-world example of a vulnerability related to dropping group privileges in the wrong order. (This vulnerability is documented in the FreeBSD security advisory FreeBSD-SA-01:11.inetd, which can be found at http://security.freebsd.org/advisories/FreeBSD-SA-01:11.inetd.asc.) The inetd server in FreeBSD contains code to handle the IDENT service, which remote users query to learn the user associated with any TCP connection on the machine. The service has an option thatallows users to place a .fakeid file in their home directory, which can contain a name the ident server provides instead of the real username. Because the ident server runs as root, the code in Listing 9-2 was used to drop privileges temporarily. Listing 9-2. Incorrect Temporary Privilege Relinquishment in FreeBSD Inetd
This code first calls seteuid() to take on the user's privileges. It then calls setegid() to take on the caller's effective group ID, but this call fails because the program has relinquished its superuser privileges. Using More Than One AccountTo understand this problem, consider a daemon that needs to use more than one user account. (This example is based on one provided by Jan Wolter, a software designer that wrote an interesting paper entitled "Unix Incompatibility Notes: UID Function Setting," available at www.unixpapa.com/incnote/setuid.html.) Here's an example of how it might be implemented:
The intent of this code is to do some processing as user1, and then assume the identity of user2 and do further processing. This implementation is flawed, however, because the call to seteuid(user2) fails because the program's effective user ID is no longer 0; it's user1. Correct code would have a seteuid(0) before the seteuid(user2) call. Auditing Privilege-Management CodeNow that you have seen a variety of vulnerabilities in code running with special privileges, you can focus on a method for auditing how those privileges are managed throughout the application's lifespan. You can use the steps in the following sections to help you decide whether privilege management has been implemented correctly and adequately inhibits users' ability to exploit the application. You consider two main cases: an application that intends to drop privileges permanently and an application that intends to drop privileges temporarily. Permanent Dropping of PrivilegesSome programs run with root privileges and want to discard these root privileges permanently. When auditing an application that runs in a privileged context and you encounter this scenario, you need to address the following points:
There are also programs that run without root privileges but want to discard one set of privileges permanently; for those programs, check the following points:
Temporary Dropping of PrivilegesIf programs need to drop their privileges temporarily, check for the following:
Function Audit Logs for Privileged ApplicationsAs a useful auditing aid, you might find it advantageous to note in your function audit logs (described in Chapter 7, "Program Building Blocks") the privileges that each function runs with when auditing applications that switch privilege contexts. This is as simple as adding in an additional two entries for a function (See Table 9-5).
The example indicates both the user and group privileges in effect when the program is run. RUID, EUID, and SUID stand for "Real UID", "Effective UID", and "Saved set UID" respectively. The next row uses RGID, EGID, SGID, and SUPP to stand for "Real GID", "Effective GID", "Saved set GID", and "Supplemental Groups" respectively. You also need to add to your notes for the function if it changes privileges throughout the course of the function, and in which cases it will change privileges. This little addition to a standard audit log allows you to quickly and accurately assess whether resource accesses within the function are potentially dangerous or not. Note You saw that the privilege management API can behave differently on different UNIX systems, and, as such, you might not be able to correctly assess what the user and group privileges will be for a particular function. In this case, you also should make a note in the function audit log if non-portable privilege API usage might cause the application to behave differently on other OSs. Privilege ExtensionsThe UNIX privilege model often comes under criticism because of its all-or-nothing design. If you're the root user, you have the unrestricted ability to wreak havoc on the system because you're granted access to any resource you want. To understand why this is a problem, return to one of the examples used in the discussion of user IDs. The ping program requires root privileges to run because it needs to create a raw socket. If a vulnerability is discovered in ping that is exploitable before it drops privileges, not only can users create a raw socket, but they can also modify any file on the system, potentially load kernel modules to hijack system functionality, delete log files, and steal sensitive data. So any program that needs to perform an operation requiring special privileges essentially puts the entire system's security at stake. Several technologies, discussed in the following sections, have been developed to combat this age-old problem. Linux File System IDsOne set of IDs not mentioned previously is relevant to code running on a Linux system. In Linux, each process also maintains a file system user ID (fsuid) and a file system group ID (fsgid). These IDs were created to address a potential security problem with signals. If you recall, when a daemon running as root temporarily drops privileges to assume a user's role, it sets its effective user ID to the ID of the less privileged user. This behavior can lead to security issues because a process's effective user ID is used in security checks throughout the kernel. Specifically, it's used to determine whether certain signals can be sent to a process from another process. Because of this checking, when the daemon assumes the effective user ID of a local user on the machine, that user might be able to send signals and perhaps even attach a debugger to the daemon. To address this issue, the Linux kernel programmers created the fsuid and fsgid to be used for all file system accesses. These IDs are usually kept 100% synced with the effective user ID, so their presence doesn't affect use of the normal privilege-management APIs. However, a program that wants to temporarily use a normal user's file system privileges without exposure to attacks caused by security checks based on effective IDs can simply change its file system user and group IDs with the API calls setfsuid() and setfsgid(). BSD securelevelsThe BSD securelevels kernel protection (now supported by Linux to some extent) is intended to protect the system kernel from the root user. The primary focus of securelevels is to enforce some restrictions on every user on the system, including the superuser, so that a root compromise doesn't render a machine completely vulnerable. It uses a systemwide kernel value, the "securelevel," to help decide what actions system users are allowed to perform. The different branches and versions of BSD vary in the number of levels they provide and the protection each level offers, but the idea is essentially the same in each version. The following excerpt from the init(8) man page describes the available levels:
As you can see, this systemwide setting can inhibit actions for even superusers. Although it offers a level of protection, it doesn't allow fine-tuning for specific processes and can be susceptible to bypasses by users modifying certain files and restarting the machine if they gain root access. CapabilitiesLinux has also undertaken the challenge of addressing the basic UNIX privilege shortcomings by implementing a technology known as capabilities. This model defines a set of administrative tasks (capabilities) that can be granted to or restricted from a process running with elevated privileges. Some of the defined capabilities include the following:
Being able to grant and omit certain capabilities from applications makes it possible to create processes that have one special system capability without putting the entire system at risk if it's compromised. The ping program is a perfect example. Instead of having it run with full permissions to create a raw socket, it could be granted the CAP_NET_RAW privilege. If the program is compromised, attackers can create raw sockets (which is still a breach), but can't automatically load kernel modules or mount new file systems, for example. Capabilities are applied to running processes but can also be applied to files on disk to enforce restrictions or grant special privileges when a certain binary is run (much like the setuid/setgid bits associated with a file). A process has three bitmasks of capabilities:
Although the effective set ultimately governs what a process can do, the other two sets are equally important. To see why, imagine that the ping program has only the CAP_NET_RAW capability in its effective set, but its permitted set includes a series of other random capabilities, such as CAP_SYS_MODULE. In this case, if users did compromise the ping program, they could enable the CAP_SYS_MODULE capability (thus adding it to the effective set) by using the sys_capset() system call and load kernel modules as a result. File capabilities are similar, except they're associated with a file. A file has three capability sets also, but these sets differ slightly:
|
Saturday, November 21, 2009
Privilege Vulnerabilities
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment