Links


UNIX provides two mechanisms for users to link fileshard links and soft links. Hard links allow users to create a single file with multiple names that can be located in different directories. Symbolic links allow users to create a special file that points to a file or directory in a different location. Attackers have used both mechanisms to subvert file system interaction code, so you examine them in detail in the following sections.

Symbolic Links

Symbolic links, also known as symlinks or soft links, allow users to create a file or directory that points to another file or directory. For example, an administrator can make a symbolic link called /home that points to the /mnt/disks/disk3a/ directory. Users could then work with files in their home directories in /home/, and everything would be redirected behind the scenes to the disk3a directory. Similarly, a user could make a symbolic link named computers in his home directory that points to the system file /etc/hosts. If the user opens computers for reading, he is actually opening the /etc/hosts file, but it would appear as though the file is in the user's home directory.

Symbolic links, created with the symlink() system call, are actually special small files placed in the file system. Their inodes are marked as a type symbolic link, and their actual file contents are a file path. When the kernel is resolving a pathname, if it encounters a symbolic link file, it reads in the file path in the symbolic link, follows the symlink's file path until it's complete, and then resumes its original path traversal. The file path in the symlink can be an arbitrary pathname, as long as it's valid enough to get the kernel to a destination.

Figure 9-5 shows what soft links look like at the directory entry level. In this figure, you have two directories. The name of the top directory isn't visible in the diagram, but assume it's thatdir. Say you're in the bottom directory, inode 1100, and you open the test.txt file. It has the inode 1300, and you can see it's a symbolic link inode. The kernel automatically opens the symbolic link file at inode 1300 and reads in the file path ../thatdir/fred.txt. The kernel opens ../ and goes back to inode 200. It then opens thatdir and enters inode 1000 (the top directory). It looks up fred.txt and goes to inode 500, which is the text file.

Figure 9-5. Symbolic link diagram


Symlink Syscalls

Because symbolic links are actually files on the file system, system calls can react to their presence in two ways. Some system calls follow symbolic links automatically, and others operate on the special symbolic link file. The following calls have symlink-aware semantics:

  • If unlink() is provided a file that's a symbolic link, it deletes the symbolic link, not the target.

  • If lstat() is provided a file that's a symbolic link, it returns the information about the symbolic link, not about its target.

  • If lchown() is provided a file that's a symbolic link, it changes the user and group of the symbolic link file, not the target.

  • readlink() is used to read the contents of the symbolic link file specified in its argument.

  • If rename() has a from argument that's a symbolic link, the symbolic link file is renamed, not its target. If rename() has a to argument that's a symbolic link, the symbolic link file is overwritten, not its target.

Symbolic Link Attacks

Symbolic links can be used to coerce privileged programs into opening sensitive files. For example, consider a privileged program that reads an optional configuration file from a user's home directory. It has the following code:

void start_processing(char *username) {     char *homedir;     char tmpbuf[PATH_MAX];     int f;     homedir=get_users_homedir(username);     if (homedir)     {         snprintf(tmpbuf, sizeof(tmpbuf),             "%s/.optconfig", homedir);         if ((f=open(tmpbuf, O_RDONLY))>=0)         {             parse_opt_file(tmpbuf);             close(f);         }         free(homedir); } ...


This code looks in a user's home directory to see whether that user has a .optconfig file. If the file is present, the program opens that file and reads in optional configuration entries. You might think this behavior is safe as long as the file-parsing capabilities of parse_opt_file() are safe, but this is where link attacks can come into play. If attackers issue a command like the following:

$ ln -s /etc/shadow ~/.optconfig


They would create a symbolic link to the shadow password file in their home directory named .optconfig. The privileged program could then be tricked into opening and parsing the shadow password file, which could lead to a security vulnerability if it exposes secret hash information.

Some older UNIX variants had a symbolic link problem with their core-dumping functionality. In UNIX, if a program crashes, the kernel can write the contents of that program's memory to a core file on the file system. This file is useful for debugging program crashes. In HPUX, Digital Unix, and probably a few other older systems, the kernel follows symbolic links when creating this core file. A normal user could, therefore, create a symbolic link to an important file, run a setuid root program, and crash it somehow, and then the kernel would write a memory dump over the important file. The attack would look something like this:

$ export SOMEVAR=" + + " $ ln -s ~root/.rhosts core $ ./runandcrashsuid.sh $ rsh 127.0.0.1 -l root /bin/sh -i #


The environment variable SOMEVAR contains the string + + on its own line, which would end up in the memory dump. The memory dump would replace root's .rhosts file, which specifies which hosts and users are allowed to log in as root on the machine without authenticating. The remote shell daemon interprets the + + line as indicating that any user from any machine is allowed to log in to the host as root. Users would then be allowed to start a shell on the machine as root.

Creation and Symlinks

The open() system call has an interesting nuance when creating files that end in symbolic links. Say you have this empty directory:

/home/jim/test


Then you add a symbolic link to this directory:

$ ln -s /tmp/blahblah /home/jim/test/newfile


This command creates a symbolic link at /home/jim/test/newfile that points to /tmp/blahblah. For now, assume the /tmp/blahblah file doesn't exist on the file system. Now try to create a file with open(), using the following call:

open("/home/jim/test/newfile", O_RDWR|O_CREAT, 0666);


You're telling open() that it should open a file for reading and writing, creating it if necessary from the location /home/jim/test/newfile. That location is a symbolic link pointing to /tmp/blahblah. The open() function actually creates a new file in /tmp/blahblah!

This behavior has interesting consequences from a security perspective. Code that has file creation semantics when it opens a file can be tricked into creating files anywhere on the file system if you can get a symbolic link in the right place. To prevent this behavior, application developers can specify the O_EXCL flag along with the O_CREAT flag, which indicates that the open() call must create a unique file (not return an already existing file) and prevents open() from dereferencing symbolic links in the last component. Another flag to open(), O_NOFOLLOW, also makes sure that open() doesn't follow a symbolic link if it's the last component of the specified filename, but it can be used when the program allows opening an existing file as long as it isn't a symbolic link.

Note

The O_NOFOLLOW flag isn't a portable solution that developers can use; it's a FreeBSD extension that's now supported by Linux, too (as of version 2.1.126). When you're auditing an application that relies on this flag to provide security, remember that some target platforms might ignore it.


Accidental Creation

In some situations, the mere creation of a file can be an undesired behavior, even if it's not malleable by unprivileged users. If an application uses a fopen() call with a writeable mode, it uses open() with an O_CREAT flag, and the kernel creates the requested file. Keep this in mind when you see custom-created protections for file attacks; developers might inadvertently use an open() that's capable of creating a file as part of the initial security check. Either situation could create a file in the file system that hampers the system's functionality, such as /etc/nologin. The presence of the /etc/nologin file prohibits any non-root users from logging in to the system. Similarly, if an empty /etc/hosts.allow file is created, all TCP-wrapped services deny incoming connections.

Attacking Symlink Syscalls

It's essential to understand that although the unlink(), lstat(), lchown(), readlink(), and rename() functions operate on a symbolic link file instead of following it to its target file, these functions do follow symbolic links for every path component except the last one. To understand this concept, imagine you have the following files in your current directory:

drwx------    2 jm       jm             96 Dec 31 09:06 ./ drwx------    3 jm       jm             72 Dec 31 09:05 ../ -rw------     1 jm       jm              0 Dec 31 09:06 testfile lrwxrwxrwx    1 jm       jm              8 Dec 31 09:06  testlink -> testfile


If you use unlink("testlink"), it should end up deleting the symbolic link file testlink instead of the target, testfile. As you can see in the following code, that's exactly what happened:

drwx------    2 jm       jm             72 Dec 31 09:09 ./ drwx------    3 jm       jm             72 Dec 31 09:05 ../ -rw------     1 jm       jm              0 Dec 31 09:06 testfile


This behavior is what you'd expect from the five system calls listed previously. Now take a look at how they do follow symbolic links. Assume you restore the directory to the way it was and also add one more symbolic link:

drwx-------   2 jm       jm            128 Dec 31 09:14 ./ drwx------    3 jm       jm             72 Dec 31 09:05 ../ lrwxrwxrwx    1 jm       jm              1 Dec 31 09:12 testdirlink -> ./ -rw-------    1 jm       jm              0 Dec 31 09:06 testfile lrwxrwxrwx    1 jm       jm              8 Dec 31 09:14 testlink -> testfile


If you use unlink("testdirlink/testlink"), you end up with the following:

drwx------    2 jm       jm            104 Dec 31 09:16 ./ drwx------    3 jm       jm             72 Dec 31 09:05 ../ lrwxrwxrwx    1 jm       jm              1 Dec 31 09:12 testdirlink -> ./ -rw------     1 jm       jm              0 Dec 31 09:06 testfile


What happens is that unlink() follows the symbolic link testdirlink and then deletes the symbolic link testlink. The symlink-aware system calls still follow symbolic links; however, they don't follow the last component if it's a symbolic link. Attackers can still play games with these system calls, but they must use symbolic links in the paths of file arguments they provide.

Hard Links

Hard links allow users to create multiple filenames on a file system that all refer to the same underlying file. For example, on one particular OpenBSD machine, the /usr/bin/chfn, /usr/bin/chpass, and /usr/bin/chsh files refer to the same program file, located on the disk at inode 24576. This chpass/chfn/chsh program is written so that it looks at what name it runs as and changes its behavior accordingly. This way, the same binary works as expected regardless of whether the the user ran it using the chpass command, the chfn command, or the chsh command.

A hard link is created when you add a new directory entry that points to an already existing file by using the link() system call. Basically, what you're doing is creating multiple directory entries that all point to the same underlying inode. Every time you add a new link to an existing inode, that inode's link count goes up. Using the previous example, the link count of inode 24576, the chpass/chfn/chsh program file, is three because three directory entries reference it.

Figure 9-6 shows what a hard link looks like in actual directory files. You have two directories on the left, one with an inode of 1000 and one with an inode of 1100. The top directory has a file named fred.txt that points to inode 500. The bottom directory has a file named test.txt that also points to inode 500. You could say that fred.txt is a hard link to test.txt, or vice versa, as they both reference the same underlying file.

Figure 9-6. Hard links


Inode 500 has a link count attribute of two, meaning two directory entries refer to the file. Every time a new hard link is created, the link count is incremented. If a user deletes fred.txt or test.txt, the link count is decremented by one. The inode isn't released until all relevant names are removed, reducing the link count to zero, and all processes have closed any open file descriptors referencing inode 500.

Hard links appear to be separate files, with separate pathnames, but they refer to the same underlying inode. So if a file has multiple hard links, and the permissions or ownership IDs change for one of them, all the other hard links reflect those changes.

Hard links don't work across file systems because a directory entry can't point to an inode on an different file system; this limitation makes hard links less flexible on UNIX systems that have several mounted partitions. Another limitation is that normal users are allowed to create hard links only to files, not to directories, because creating infinite loops in the directory tree is quite simple, so you don't want normal users to have this capability. Therefore, creating directory hard links is a privilege reserved for the superuser. You can create infinite loops with symbolic links, too, but the kernel has code to detect whether this has occurred and return an appropriate error.

Attacks

From a security perspective, the critical feature of hard links is that you can create links to various files without needing any particular privileges, which could lead to possible security problems. For example, say you want to write exploits for certain setuid binaries on a system, but you're concerned that the administrator might delete them. You don't have the permissions necessary to copy them, but you could create hard links to those binaries in a directory you have control over. If the administrator deletes the binaries later, your hard links still refer to them, and you might still have time to construct an attack.

This technique might also prove useful when you want to prevent a program from deleting a file. You could create a hard link to that file that would still be present after the program attempts to delete the original file. You don't need any special permissions or ownership on that file to create the link, either.

Another thing to note about hard links: If you create a hard link to a file you don't own in a sticky directory, you can't delete the hard link because the sticky semantics prevent you from unlinking a file that isn't yours. This might prove useful when mounting sophisticated file-based attacks against a privileged application.

Sensitive Files

Hard links can be quite useful in launching attacks against privileged processes. They are more limited in utility than soft links, but they can come in handy sometimes. They are most useful when privileged processes open existing files and modify their content or change their ownership or permission. Take a look at this simple code excerpt:

    fd = open("/home/jm/.conf", O_RDWR);     if (fd<0)         die("open");     write(fd, userbuf, userlen);


Assume this code runs in a setuid root application with effective root privileges. It opens the /home/jm/.conf file, if it exists, and writes some data to it. Assume the .conf file is in your home directory and you have total control over it. Assume you can control some data that gets written in the call to write(), and your home directory is in the same file system as the /etc file system.

Exploiting this code with a hard link would be quite straightforward. You'd simply do something like this:

$ cd /home/jm $ ln /etc/passwd .conf $ runprog $ su evil #


First, you create a hard link so that the .conf file is linked to the /etc/passwd authentication file. Then you run the vulnerable program, which opens the file for writing as root. It writes out some information you control to the password file, which adds a new root account with no password. You then use su to switch to that account and claim root access.

In general, this kind of attack can be useful if the privileged application reads from a file without first relinquishing its privileges. If the application opens a file that's really a hard link to a critical system file, such as /etc/shadow, you can probably elicit an error message that might expose some secret information.

Remember that permission and ownership changes affect the underlying inode of a hard link, so you should also check for code that might alter a privileged file's permissions. Take a look at the following code:

    fd = open("/home/jm/.conf", O_RDWR);     if (fd<0)         die("open");     fchmod(fd, 644);     exit(1);


In this code, the /home/jm/.conf file is opened, and then permissions are set to 644. One possible attack is linking the pathname being opened with some other file that has tight permissions, such as /etc/shadow. If you create a hard link to /etc/shadow, and the code changes its permissions from 0600 to 0644, every user on the system could read the authentication database.

Circumventing Symbolic Link Prevention

In general, soft link attacks are more flexible and powerful. However, because special API calls deal with symbolic links, and symbolic link attacks have been widely published, developers are far more likely to prevent symbolic link attacks than hard link attacks.

In general, developers can use the lstat() function to analyze a file and determine whether it's a symbolic link. Note that lstat() can't distinguish between a hard link to a regular file and a regular file because a hard link is a legitimate directory entry. The only clue applications can use to test for hard links is to check the link count resulting from a stat(), lstat(), or fstat() function.

Here's an example of code that's vulnerable to a hard link attack (if it were being run in a privileged context):

    if (lstat(fname, &stb1) != 0)         die("file not there");     if (!S_ISREG(stbl.st_mode))         die("it's not a regular file - maybe a symlink");     fd = open(fname, O_RDONLY);


This code uses the lstat() function to make sure the provided file isn't a symbolic link. If it's a symbolic link, it doesn't pass the S_ISREG test (explained in "The stat() Family of Functions" later in this chapter). A hard link works just fine, however, causing this program to read the contents of whatever fname is hard-linked to. (Note that this code is also vulnerable to race conditions, discussed in the next section.)




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net