next up previous contents
Next: 3.2 Practical Up: 3. Host Security Problems Previous: 3. Host Security Problems

Subsections

3.1 Theory

3.1.1 Denial of Service

The Internet as a whole has adopted a primarily client-server approach to networking. This is partly because of Unix' major contribution to the structure of the current Internet, itself being client-server oriented. With the client-server approach in mind, it is important to note that the server's role is to serve the client. When the server can no longer serve the client, the server has lost its usefulness. Denial of Service (DoS) attacks do exactly that, incapacitate a server so that it cannot serve. On Unix and other machines, service may be denied from a particular port or the whole machine.

On the host itself, there are a number of DoS attacks that can be used. Among the techniques are some for taking up as many of the resources as possible, leaving little or none for other system programs. An example is password file locking, which would prevent everyone from changing their passwords.

3.1.2 Password Cracking

Unix uses a one-way encryption scheme for password security. This type of cryptography makes it improbable that someone would decrypt a password encrypted using this algorithm. The commonly used algorithms are DES3.2 and MD5. When a user logs in, the login program finds the username and encrypted password in the password file. It then takes the string the user supplied as a password, and encrypts it the same way that the one in the password file was encrypted. It then compares the result of the encryption with the password stored in the password file. If they match, the correct password was entered and the user is logged in. However, one-way encryption is in some cases vulnerable to brute-force and dictionary attacks.

3.1.2.1 Brute force attack

In this type of attack against passwords, not much intelligence is used by the password cracker. The password cracker usually is used in incremental mode, starting with the letter 'a', incrementing it and using different permutations. All these permutations are checked against the password in the password file. This type of attack requires a lot of computing power, is slow, but very effective in finding short passwords (one to four characters).

3.1.2.2 Dictionary attack

This type of attack is based on the theory that users choose passwords that are based on dictionary words. Dictionary initially referred to a language dictionary. It has since evolved to become any group of words that have the possibility of being used as passwords. The majority of these words are still based on language dictionaries. Apart from taking words and encrypting them, and comparing the result with what is in the password file, this method incorporates some intelligence which is usually configurable. As an example, a common password is 'password'. A lot of users however, will transform 'password' to 'passw0rd' or even 'password1'. Dictionary based password crackers have configurable rules to find such variations on the dictionary based words. It is this versatility that makes them very effective.

3.1.3 Misconfigured Software

It is true that a chain is only as strong as its weakest link. This fact is never as apparent as it is in misconfigured software. If the software runs from a privileged account, the results can be devastating while easy to obtain. Consider the cron daemon3.3 which usually runs as root. When executing individuals' cron files, it should drop its privileges and execute commands as that user. If misconfigured however, and the user is aware of this, illegal commands can be executed in privileged mode.

3.1.4 Buffer Overflows

The C programming language is one of the cornerstones of the Unix Operating System. It came out of Unix and is now used to write the operating system itself, and most of the utilities. Unix therefore comes standard with a C compiler, usually called gcc. C was written and chosen to be very flexible. A very famous cliche is ``C (and UNIX) allows you to do the stupid things that you want to do; in so doing it also allows you to do the clever things that you want to do''. As part of this philosophy, the C language does not do bounds checking on arrays. This ``feature'' is what has made overflowing the buffer the most popular security problem.

When the programmer writes past the bounds of the array, he may overwrite some part of memory needed by the program. When this happens, the program terminates abnormally as some of its code has been overwritten. To fully understand what happens when the buffer is overflowed, let us have a look at how memory is arranged, as shown in figure 3.1.

 
Figure 3.1: Memory arrangement
\resizebox*{9cm}{!}{\includegraphics{third.ps}}


The text region is where the program code and read-only data resides. This region is read-only, so it cannot be modified. For efficiency, on most multiuser machines, the text region can be shared among several instances of the running program. The data region holds both initialized and uninitialized data, including static variables. When memory is allocated dynamically, it is added between the stack and data regions. It is in the stack region that buffers overflow, hence it is also referred to as overflowing the stack[21].

 
Figure 3.2: Diagrammatic representation of buffer overflows
\resizebox*{10cm}{8cm}{\includegraphics{newbuff.ps}}


Figure 3.2 illustrates some points which shall be very useful in understanding the practical aspects of buffer overflows, e.g. writing exploits. The box labelled (1) shows sample code which has a potential buffer overflow. The box labelled (2) is our buffer exploit code and will be used to show some of the interactions between components to overflow the buffer. Box labelled (3) is simply the address of the code we will use to overflow our buffer. Label (4) shows frames for both the main() and function() functions. Finally, label (5) shows part of the stack when executing the function() part of the program in (1). We will assume that our stack grows downwards. In our diagram, this appears as growing to the left hand side. Let us now have a closer look at what is going on.

We start our illustration at line 8 of our program in (1), where IP also points. While executing our call to function(), our stack is as it is in (3). In main() we have 2 local variables (a and b) - these appear on the bottom of our stack. When function() is called, the argument is pushed onto the stack (seen as the actual parameter string). To call function(), the IP is then pushed onto the stack and becomes RET as shown by the arrow from IP to (5). This address is where the function will return after executing. The instruction after IP/RET is the next instruction to be executed. Inside function() itself, the first thing that is done is the procedure prolog. What happens in this process is that the old FP is pushed onto the stack. The old SP now becomes the new FP. Local variables are then allocated by subtracting their size from the SP. This then forms the new SP, with variables between the SFP and the SP as illustrated with (4). Above the SFP on the stack is our local variable, buffer. Finally, our SP is shown on top of the stack. It is worth noting that variables are referenced by their offsets from SP.

To effectively overflow our buffer, string has to be long enough be able to overwrite RET. string is filled with the address of the start of (2), in other words it is filled with (3). At line 3 of (1), when string is copied to our limited size buffer, it will overflow it and overwrite RET. RET now effectively points to (2). When the function function() finishes and needs to return to main() it cannot, because its return address has been modified. Instead of returning to main(), it returns to the overflow code. This overflow code can execute any arbitrary command, but a shell is usually executed to give root access. For more detailed technical information on buffer overflows, see [21].

3.1.5 Set User ID Files

Files on Unix have permissions and ownerships. Set User ID files are files with their set-user-ID (SUID) bit set. The SUID flag allows a file owned by user B to run with user B's privileges, even when run by user A. An example is the Unix passwd program users run to change their passwords. The program needs to write to /etc/passwd, the password file, which is owned by root. Instead of giving every user root access, the SUID bit of the passwd program is set. The users then run the program with root privileges which allow writing to /etc/passwd. The particular set of SUID files in which an attacker is interested is that of files owned by root. By compromising one of those files, root privileges can be obtained. For an introduction to SUID files, how they work and the problems associated with them, see [12].

3.1.6 Backdoors and Trojans

A trojan is a program that appears to do something useful, while covertly doing evil. A backdoor, in security terms, is an alternative entrance to the system that is not subjected to the same authorization and authentication rules as the authorized entrance. Trojan horses are the most popular way of implementing backdoors into a system. The reason is that it is easier for the administrator to find a new program on his system. Trojanning a copy of the login program for example, might appear to introduce nothing new into the system. For the most part, the login program will work as it ever did, while serving up root shells to the intruder. Trojans inserted into the system kernel are even harder to find[17]. Since upgrades of systems, especially proprietary ones, are infrequent, backdoors and trojans can exist for years on the system without ever being noticed. More information on backdoors and trojans can be obtained from [15].

3.1.7 Keystroke Logging

As the name implies, this type of attack is related to the logging of keystrokes.

3.1.7.1 Keystroke Logging

Keystroke logging is usually done in conjunction with some form of a trojan. The most popular method of doing the above is having a program that pretends to be the login program. When the [ignorant] user has typed his login name and password, the program logs the pair to a file. A number of things can be done about the user who now expects to be logged in. A popular method is then to kill the login spoofing program. After the spoofing program has been killed and the user running it logged out, the normal login program kicks in. This attack works better on console rather than remote logins. No special privilege is needed to mount this attack.

An attack that does need special privilege however is one where a user's keystrokes are read directly from their terminal. It works equally well with console or remote logins. The most frequent types of this attack involve reading the user's input to the shell after he has logged in. Variants which record the login name, password pair are available but rare. The above attack is less frequent than the one above because of the privilege that is required to mount it.

3.1.7.2 Keystroke Insertion

In the same way that keystrokes can be read from a terminal, they can also be written to a terminal. Using this form of attack, an intruder can make the user whose terminal is being attacked execute arbitrary commands. By carefully engineering the way in which this is done, the user may remain ignorant of the commands he is executing.

3.1.7.3 X Windows

The X windowing system is a Graphical User Interface (GUI) to the Unix operating system. As part of Unix's remote accessibility philosophy, X window displays can be setup to allow access from remote machines and locations. It is this access from locations apart from the current system console that introduces the problem of a security problem. Users can connect to a [misconfigured] open X display and perform keystroke logging attacks. What makes this a more serious problem is that no above user-level privilege is needed to mount this attack.


next up previous contents
Next: 3.2 Practical Up: 3. Host Security Problems Previous: 3. Host Security Problems
Shaun Bangay
1998-11-19