This section includes several case studies, including a relic from the mid-1980s implementing role-based access control, a couple of classic wrapper programs, a secure mail delivery system, and the 802.11 wireless LAN security design. We've carefully selected these examples?from real experiences?to give you insight into how others have approached difficult design problems.
The Access Control Executive (ACE) was a software system that Mark codesigned and coengineered in the mid-1980s. It provided key security services to many famous museums in Europe (and many other tightly secured locations). We include it as a case study as an example of a well-thought-out mental model.
We called the software the Access Control Executive because it ran as a background process and controlled access to all system resources. The ACE was consulted before any utility was successfully initiated; before any file was (through an application) opened, written, or closed; and before any vault (let's say) was opened. It gave a ruling on whether the action was to be permitted, denied, or modified, and this ruling was communicated back to the caller in real time.[6]
[6] The first software we know of with this capability was the TOPS-20 "Policy" program, offered by Digital Equipment Corporation (in open source!) in the early 1980s.
By design, each application program had to ask the ACE for permission to do anything risky. Yet none of them was burdened with specific code to do this. Instead, at initialization each invoked a single routine that "signed on" to the ACE, and at the same time each one modified on the fly its copy of a small part of the system's library controlling input and output, program invocation, and a few other operations.
An alternate scheme we considered was to build the applications themselves with a modified version of the library. This would have obviated the need for any changes to the application source code at all! We rejected the idea because we worried that it would burden future maintainers of the code, forcing them to understand and follow highly specialized build techniques for all the applications.
Another feature that distinguished the ACE was what we called the capability cube. Part of the specification of the system was that the customer had to be able to control all actions on the basis of five parameters of the request:
Job category of the person making the request (e.g., security guard)
Time
Day of the week (or date)
Physical location of the requestor
Application used to make the request
We decided to treat these decision combinations as a five-dimensional matrix, a kind of cube. Once that security model had been selected, it was easy then to write utilities to build, maintain, and inspect the cube. Because the cube was a sparse matrix, we added utilities to compress and expand it as needed to conserve memory and disk space. We wrote a utility to control the user/role table, too, and also supplied a way to build a map showing the relationship between physical terminal locations.[7]
[7] ACE used the map to ensure that "silent alarms" triggered at one terminal were not displayed on another screen in the same room.
When the ACE got a request, it simply consulted the cube to see if an action was permitted, and relayed the information as a decision.
ACE also implemented a peculiar twist on the playpen technique we discussed earlier. We tried to model it after the construction of Mayan temples, which remained strong even after multiple earthquakes. As we understood it, the Mayans made their temple walls several meters thick, for strength; but much of the inside was a rubble-filled cavity. As earthquakes shook the walls (as they still do) the rubble jumps and dissipates much of the kinetic energy. Aspiring to do the same, we engineered our system so that it would, under attack, collapse inwardly in stages, appearing at each phase to have been utterly compromised but in reality still mounting defenses against the attacker.[8]
[8] The metaphors seem a little jumbled?there was an "onion layer" element, too?but we are accurately reporting how we envisaged the system as we built it. And it worked!
Another notable aspect of the project was the way its design goals were specified. Our contracted-for goal was to protect critical resources for specified periods of time:
For about an hour against attack by a well-informed expert malefactor
For up to a day of continual effort by a determined and computer-savvy attacker from the community of authorized users
For up to a week of generally aimless or brute-force attack by a casual user
This case study teaches several lessons. The following are especially important:
The ACE design shows clearly how a design metaphor can be applied to envision a software design
The design also provides a useful example of how a centralized authorization mechanism might work
The overflow.c program is a wrapper that (as far as we can tell) works. AusCERT, the famous Australian security team, released it in 1997. Here's what the documentation at ftp://ftp.auscert.org.au/pub/auscert/tools/overflow_wrapper says about it:
This wrapper is designed to limit exploitation of programs which have command-line argument buffer overflow vulnerabilities. The vulnerable program is replaced by this wrapper. The original vulnerable program is moved to another location and its permissions restricted. This wrapper checks each argument's length to ensure it doesn't exceed a given length before executing the original program.
Here is what the code looks like (with most of the comments/documentation removed):
static char Version[] = "overflow_wrapper-1.1 V1.1 13-May-1997"; #include <stdio.h> #include <syslog.h> /* * This wrapper will exit without executing REAL_PROG when * given any command line arguments which exceed MAXARGLEN in length. */ main(argc,argv,envp) int argc; char *argv[]; char *envp[]; { int i; for (i=0; i<argc; i++) { if (strlen(argv[i]) > MAXARGLEN) { fprintf(stderr,"You have exceeded the argument \ length ...Exiting\n"); #ifdef SYSLOG syslog(LOG_DAEMON|LOG_ERR,"%.32s: possible buffer \ overrun attack by uid %d\n", argv[0], getuid( )); #endif exit(1); } } execve(REAL_PROG, argv, envp); perror("execve failed"); exit(1); }
Breathtakingly simple, isn't it? It aspires to a significantly more modest functionality than smrsh, as you will soon see. There is no complex character smashing. It seems to us to be foolproof, an excellent example both of sound design and satisfactory implementation.
This case study teaches us an important lesson about the value of simplicity in a design.
The smrsh program is a Unix utility written by an expert programmer. It was created as a security retrofit for Sendmail. The idea was to compensate for Sendmail's many security design flaws by restricting the programs to which Sendmail itself can pass control.
Here is the official description:
The smrsh program is intended as a replacement for /bin/sh in the program mailer definition of Sendmail. It's a restricted shell utility that provides the ability to specify, through the /etc/smrsh directory, an explicit list of executable programs available to Sendmail. To be more accurate, even if somebody with malicious intentions can get Sendmail to run a program without going through an aliases or forward file, smrsh limits the set of programs that he or she can execute. When used in conjunction with Sendmail, smrsh effectively limits Sendmail's scope of program execution to only those programs specified in smrsh's directory.
That assessment turns out to be too optimistic. So too were these comments within the program:
** The following characters are completely illegal:
** < > ^ & ` ( ) \n \r
** The following characters are sometimes illegal:
** | &
** This is more restrictive than strictly necessary.
Despite these comments?and despite the fact that smrsh was explicitly designed to prevent security compromises resulting from manipulation of the shells that Sendmail invoked?smrsh was found to have two such vulnerabilities. Actually, early versions of the utility did not have the bugs; they were introduced during code maintenance, and discovered in the version of smrsh released with Sendmail 8.12.6.
If you are comfortable reading C code, take a look at this extract from the buggy version. (Even those who don't know C may get some helpful general impressions from this code.) Remember: this is a wrapper you are looking at. The goal is to sanitize input and restrict which programs can be run as a consequence of a command-line parameter.
/* ** Disallow special shell syntax. This is overly restrictive, ** but it should shut down all attacks. ** Be sure to include 8-bit versions, since many shells strip ** the address to 7 bits before checking. */ if (strlen(SPECIALS) * 2 >= sizeof specialbuf) { #ifndef DEBUG syslog(LOG_ERR, "too many specials: %.40s", SPECIALS); #endif /* ! DEBUG */ exit(EX_UNAVAILABLE); } (void) sm_strlcpy(specialbuf, SPECIALS, sizeof specialbuf); for (p = specialbuf; *p != '\0'; p++) *p |= '\200'; (void) sm_strlcat(specialbuf, SPECIALS, sizeof specialbuf); /* ** Do a quick sanity check on command line length. */ if (strlen(par) > (sizeof newcmdbuf - sizeof CMDDIR - 2)) { (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s: command too long: %s\n", prg, par); #ifndef DEBUG syslog(LOG_WARNING, "command too long: %.40s", par); #endif /* ! DEBUG */ exit(EX_UNAVAILABLE); } q = par; newcmdbuf[0] = '\0'; isexec = false; while (*q != '\0') { /* ** Strip off a leading pathname on the command name. For ** example, change /usr/ucb/vacation to vacation. */ /* strip leading spaces */ while (*q != '\0' && isascii(*q) && isspace(*q)) q++; if (*q == '\0') { if (isexec) { (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s: missing command to exec\n", prg); #ifndef DEBUG syslog(LOG_CRIT, "uid %d: missing command to exec", (int) getuid()); #endif /* ! DEBUG */ exit(EX_UNAVAILABLE); } break; } /* find the end of the command name */ p = strpbrk(q, " \t"); if (p == NULL) cmd = &q[strlen(q)]; else { *p = '\0'; cmd = p; } /* search backwards for last / (allow for 0200 bit) */ while (cmd > q) { if ((*--cmd & 0177) == '/') { cmd++; break; } } /* cmd now points at final component of path name */ /* allow a few shell builtins */ if (strcmp(q, "exec") == 0 && p != NULL) { addcmd("exec ", false, strlen("exec ")); /* test _next_ arg */ q = ++p; isexec = true; continue; } else if (strcmp(q, "exit") == 0 || strcmp(q, "echo") == 0) { addcmd(cmd, false, strlen(cmd)); /* test following chars */ } else { char cmdbuf[MAXPATHLEN]; /* ** Check to see if the command name is legal. */ if (sm_strlcpyn(cmdbuf, sizeof cmdbuf, 3, CMDDIR, "/", cmd) >= sizeof cmdbuf) { /* too long */ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s: %s not available for sendmail programs \ (filename too long)\n", prg, cmd); if (p != NULL) *p = ' '; #ifndef DEBUG syslog(LOG_CRIT, "uid %d: attempt to use %s \ (filename too long)", (int) getuid(), cmd); #endif /* ! DEBUG */ exit(EX_UNAVAILABLE); } #ifdef DEBUG (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "Trying %s\n", cmdbuf); #endif /* DEBUG */ if (access(cmdbuf, X_OK) < 0) { /* oops.... crack attack possiblity */ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT, "%s: %s not available for sendmail programs \ \n",prg, cmd); if (p != NULL) *p = ' '; #ifndef DEBUG syslog(LOG_CRIT, "uid %d: attempt to use %s", (int) getuid(), cmd); #endif /* ! DEBUG */ exit(EX_UNAVAILABLE); }
This code excerpt is reminiscent of many programs we've written and others we've never finished writing: the logic became so intricate that we, having studied hundreds of similar efforts, despaired of ever being confident of its correctness and searched out another approach.
At any rate, there is a problem. At a time when the code was running on and protecting thousands of sites around the world, the following "security advisory" was issued by a group called "SecuriTeam":
It is possible for an attacker to bypass the restrictions imposed by The Sendmail Consortium's Restricted Shell (smrsh) and execute a binary of his choosing by inserting a special character sequence into his .forward file. smrsh is an application intended as a replacement for sh for use in Sendmail. There are two attack methods, both of which are detailed below.
Method one:This method takes advantage of the application's implementation of the `||' command. The process is best explained with an example:
$ echo "echo unauthorized execute" > /tmp/unauth $ smrsh -c ". || . /tmp/unauth || ." /bin/sh: /etc/smrsh/.: is a directory unauthorized execute/tmp/unauth is executed despite the fact that it is not located in the smrsh restricted directory /etc/smrsh. This happens because smrsh first checks for `.', which exists, and does no further verification on the files listed after `||'. The same attack would look like the following in the attacker's .forward file:
"| . \|| . /tmp/unauth \|| ."Method two:This method takes advantage of the following routine from smrsh.c:
/* search backwards for last / (allow for 0200 bit) */ while (cmd > q) { if ((*--cmd & 0177) == `/') { cmd++; break; } }While this exploit obviously removes the restrictions imposed by smrsh, it also allows users to execute programs on systems that they do not have shell access to. Utilizing either of the above-described methods, an attacker who can modify his own .forward file can execute arbitrary commands on the target system with the privileges of his own account. Systems that forbid shell access generally do not have tightened local security. The ability to execute arbitrary commands through the smrsh vulnerability opens the target system to local privilege escalation attacks that otherwise would not be possible.
Of course, it's not remarkable that the code has these simple errors. (A patch was executed and released within days by sendmail.org.) And we don't hold it up as an example simply for the jejune thrill of exhibiting a wrapper program that can itself be subverted by command-line manipulation. Rather, we cite this example because we think it makes our case. Without a sound underlying security design?and unless maintenance is consistent with that of design and is subjected to the same level of quality control?even the most expert and motivated programmer can produce a serious vulnerability.
As a matter of fact, we could have shown off a few of our own bugs and made the same point!
Wietse Venema at IBM's Thomas J. Watson Research Center set out to write a replacement for the problematic Sendmail Mail Transfer Agent (MTA[9]). In so doing, he created an extraordinary example of the design of a secure application[10]. With permission from Dr. Venema, we quote here (with minor editing for style consistency) his security design discussion, explaining the architectural principles that he followed for the Postfix mailer. (The descriptions of Postfix were excerpted from material at Dr. Venema's web site, http://www.porcupine.org/. Note that we've given this case study more space than most of the others because the lessons it teaches are so significant.)
[9] An MTA runs as a network service and is responsible for incoming and outgoing delivery of email to the intended recipient, on the intended computer system. It is the core of modern email delivery systems.
[10] Dr. Venema, we note with organizational pride, is a former chairman (as are each of your authors) of FIRST.
By definition, mail software processes information from potentially untrusted sources. Therefore, mail software must be written with great care, even when it runs with user privileges and even when it does not talk directly to a network.
Postfix is a complex system. The initial release has about 30,000 lines of code (after deleting the comments). With a system that complex, the security of the system should not depend on a single mechanism. If it did, one single error would be sufficient to compromise the entire mail system. Therefore, Postfix uses multiple layers of defense to control the damage from software and other errors.
Postfix also uses multiple layers of defense to protect the local system against intruders. Almost every Postfix daemon can run in a chroot jail with fixed low privileges. There is no direct path from the network to the security-sensitive local delivery programs?an intruder has to break through several other programs first. Postfix does not even trust the contents of its own queue files or the contents of its own IPC messages. Postfix filters sender-provided information before exporting it via environment variables. Last but not least, no Postfix program is setuid. [The setuid feature, which is described in more detail in Chapter 4, is a Unix mechanism for running a program with a prespecified user identification. This enables the programmer to ensure that the program runs with known privileges and permissions.]
Postfix is based on semiresident, mutually cooperating processes that perform specific tasks for each other, without any particular parent-child relationship. Again, doing work in separate processes gives better insulation than using one big program. In addition, the Postfix approach has the following advantage: a service, such as address rewriting, is available to every Postfix component program, without incurring the cost of process creation just to rewrite one address.
Postfix is implemented as a resident master server that runs Postfix daemon processes on demand (daemon processes to send or receive network mail messages, daemon processes to deliver mail locally, and so on). These processes are created up to a configurable number, and they are reused a configurable number of times and go away after a configurable amount of idle time. This approach drastically reduces process creation overhead while still providing good insulation from separate processes.
As a result of this architecture, Postfix is easy to strip down to the bare minimum. Subsystems that are turned off cannot be exploited. Firewalls do not need local delivery. On client workstations, one disables both the SMTP listener and local delivery subsystems, or the client mounts the maildrop directory from a file server and runs no resident Postfix processes at all.
Now, let's move on to the next stage of the development lifecycle and discuss the design of Postfix itself.
As we described earlier, most Postfix daemon programs can be run at fixed low privilege in a jail environment using the Unix chroot function. This is especially true for the programs that are exposed to the network: the SMTP server and SMTP client. Although chroot, even when combined with low privilege, is no guarantee against system compromise, it does add a considerable hurdle. And every little bit helps.
Postfix uses separate processes to insulate activities from each other. In particular, there is no direct path from the network to the security-sensitive local delivery programs. First an intruder has to break through multiple programs. Some parts of the Postfix system are multithreaded. However, all programs that interact with the outside world are single-threaded. Separate processes give better insulation than multiple threads within a shared address space.
No Postfix mail delivery program runs under the control of a user process. Instead, most Postfix programs run under the control of a resident master daemon that runs in a controlled environment, without any parent-child relationship to user processes. This approach eliminates exploits that involve signals, open files, environment variables, and other process attributes that the Unix system passes on from a possibly malicious parent to a child.
No Postfix program is setuid. In our opinion, introducing the setuid concept was the biggest mistake made in Unix history. The setuid feature (and its weaker cousin, setgid) causes more trouble than it is worth. Each time a new feature is added to the Unix system, setuid creates a security problem: shared libraries, the /proc file system, multilanguage support, to mention just a few examples. So setuid makes it impossible to introduce some of the features that make Unix successors such as plan9 so attractive?for example, per-process filesystem namespaces.
Early in the process of designing Postfix, the maildrop queue directory was world-writable, to enable local processes to submit mail without assistance from a setuid or setgid command or from a mail daemon process. The maildrop directory was not used for mail coming in via the network, and its queue files were not readable for unprivileged users.
A writable directory opens up opportunities for annoyance: a local user can make hard links to someone else's maildrop files so they don't go away and/or are delivered multiple times; a local user can fill the maildrop directory with garbage and try to make the mail system crash; and a local user can hard link someone else's files into the maildrop directory and try to have them delivered as mail. However, Postfix queue files have a specific format; less than one in 1012 non-Postfix files would be recognized as a valid Postfix queue file.
Because of the potential for misbehavior, Postfix has now abandoned the world-writable maildrop directory and uses a small setgid postdrop helper program for mail submission.
As mentioned earlier, Postfix programs do not trust the contents of queue files or of the Postfix internal IPC messages. Queue files have no on-disk record for deliveries to sensitive destinations such as files or commands. Instead, programs, such as the local delivery agent, attempt to make security-sensitive decisions on the basis of first-hand information.
Of course, Postfix programs do not trust data received from the network, either. In particular, Postfix filters sender-provided data before exporting it via environment variables. If there is one lesson that people have learned from web site security disasters it is this one: don't let any data from the network near a shell. Filtering is the best we can do.
Postfix provides a number of defenses against large inputs:
Memory for strings and buffers is allocated dynamically, to prevent buffer overrun problems.
Long lines in message input are broken up into sequences of reasonably-sized chunks, and are reconstructed upon delivery.
Diagnostics are truncated (in one single place!) before they are passed to the syslog interface, to prevent buffer overruns on older platforms. However, no general attempt is made to truncate data before it is passed to system calls or to library routines. On some platforms, the software may still exhibit buffer overrun problems, as a result of vulnerabilities in the underlying software.
No specific attempt is made to defend against unreasonably long command-line arguments. Unix kernels impose their own limits, which should be sufficient to deal with runaway programs or malicious users.
Other Postfix defenses include:
The number of in-memory instances of any object type is limited, to prevent the mail system from becoming wedged under heavy load.
In case of problems, the software pauses before sending an error response to a client, before terminating with a fatal error, or before attempting to restart a failed program. The purpose is to prevent runaway conditions that only make problems worse.
[That concludes the "guest selection" authored by Dr. Venema. Thanks, Wietse!]
This case study teaches several lessons. The following are especially important:
The masterful use of compartmentalization in Postfix to separate the different functions of mail delivery is worth careful study.
Most MTAs in use on Unix systems make use of the setuid capability. This design shows that it is not necessary. Everything that would have been accomplished by the setuid method was done by careful use of the existing file access controls available in the operating system.
TCP Wrappers, also written (and given away) by the ubiquitous Wietse Venema, is a very popular tool for helping secure Unix systems. Although Dr. Venema has chosen to call it a "wrapper," we somehow think it is better characterized as an example of interposition. In any event, the way it works is both elegant and simple. Figure 3-5 shows its operation.
Whenever a new network connection is created, Unix's inetd is typically responsible for invoking the appropriate program to handle the interaction. In the old days, inetd would just start up the program?let's say the telnet handler?and pass the connection information to it. But when TCP Wrappers is installed, it gets invoked by inetd instead of the handler. It performs some sanity checking, logging, and so forth, and then?if continuing is consistent with the system's security policy?it passes the connection to the original target program.
What does Dr. Venema say about it? His claims are modest:
The package provides tiny daemon wrapper programs that can be installed without any changes to existing software or to existing configuration files. The wrappers report the name of the client host and of the requested service; the wrappers do not exchange information with the client or server applications, and impose no overhead on the actual conversation between the client and server applications.
Take a look at what a recent RAND/DARPA study on critical infrastructure protection issues ("The Day After... In Cyberspace II") says about this approach:
There are thousands of existing information systems and components supporting the national information infrastructure, including individual PSTN switches, pipeline control systems, the air traffic control system, Internet routers, and so on. It is clearly not possible, in the next decade or two, to redesign and reprogram all these systems to enhance their security significantly. Is it possible, however, to retrofit these systems with special hardware/software devices for greater security?
An analogy might be the "TCP Wrapper" technology pioneered by Wietse Venema and others that is used as a software retrofit on a key Internet protocol. Are other security-enhancing "wrappers" possible in other circumstances? The entire topic of retrofitting existing systems could use substantial R&D if significant progress on infrastructure security is to be made on any reasonable time scale.
TCP Wrappers is an inspiring example of what is possible with intelligent security retrofitting. It teaches us several important lessons:
It is possible to achieve a great deal of security, even in the absence of source code to the software being retrofitted with additional security features.
A small set of simple security enhancements to an existing piece of software can greatly improve its security and usability. The event logging capability of TCP Wrappers alone made the retrofit worth the effort and has aided countless Internet sites in their efforts to secure their networks.
The enormously popular IEEE 802.11 suite of protocols provides a standard for wireless local area networking over radio frequencies. One of the early security requirements for 802.11 was to provide security that was "equivalent" to having a wired (i.e., private) local area network, so that only authorized devices and users could send or receive the data packets. To provide this security, the standard defined Wired Equivalence Protocol (WEP) encryption and authentication mechanisms. Although the goals were admirable, unfortunately WEP turned out to be a perfect example of how not to design security, and we think looking at the mistakes that were made in WEP provide valuable lessons to be learned.[11]
[11] For more information, refer to "Intercepting Mobile Communications: The Insecurity of 802.11," by Nikita Borisov, Ian Goldberg, David Wagner. See http://citeseer.nj.nec.com/borisov01intercepting.html for details.
First, WEP was specified to be optional, and it was therefore allowable for manufacturers to ship access points with WEP turned off by default. Unfortunately, the vast majority of users simply never turn the WEP option on, perhaps due to laziness or fear of the unknown. Studies have shown that between one-third and two-thirds of all installed and active access points do not have WEP turned on, allowing attackers direct access to wireless networks.
Second, despite the efforts of the design committee, WEP has no fewer than four significant cryptographic design errors. To understand the errors, let's look at the basic design of WEP. An unencrypted 802.11 packet has two parts: a header and a body with data:
[header] [ data body]
WEP adds a 24-bit initialization vector (IV) in plaintext, encrypts the body, and appends a 32-bit CRC integrity check (also encrypted) as follows:
[header] [24 bit IV] [encrypted body] [encrypted 32 bit CRC]
The encryption is done by taking up to 104 bits of a shared secret key and adding the plaintext IV, to form a 128-bit encryption key. This key is used with the RC4 encryption algorithm to create a stream of bytes that are exclusive OR'ed into the stream of body and check bytes.
Authentication of the client station to the access point is done with a challenge-response protocol: the access point picks a random 128-bit challenge and sends it to the client station. The station then has to WEP-encrypt the challenge packet and send the encrypted version back to the access point (AP):
This overall design of WEP has the following major cryptographic errors:
24 bits of the RC4 encryption key are known (as the plaintext IV that is sent before the encrypted data). Attackers are thus able to recover the entire 128-bit key simply by observing 5 to 6 million encrypted packets.
This scheme is essentially a one-time pad, which is provably secure if and only if each pad (IV) is used only once. Unfortunately with only 24 bits of IV, each encryption pad will be reused frequently. If two packets are encrypted with the same IV, simply exclusive OR'ing the two encrypted packets together gives the attacker the exclusive or of the plaintexts.
The integrity check is a simple CRC-32 checksum, which is not cryptographically strong. With relatively small effort, attackers can successfully change individual bits in the encrypted body without disrupting the checksum. This is known as a bit-twiddling attack.
Simply observing one successful client station authentication gives the attacker a plaintext-ciphertext pair. Exclusive OR'ing these together gives the encryption pad for that particular IV. As the sender is free to choose the IV for each packet that is sent, an attacker can now successfully encrypt and send arbitrary packets using that one IV.
This case study teaches several lessons. The following are especially important:
Cryptography is difficult to get right.
It's critical to get open external review of any cryptographic design of relevance. If someone is unwilling to publish his cryptographic design, then it is likely broken. To us, it might as well be.
Users are reluctant to turn on optional security features, so it is safer to default a security feature to "on," giving the user the option to turn it off where necessary.