Cyber Security Expo
Crafting Symlinks for Fun and Profit by Shaun Colley on 12/04/04

Due to the recent hype of the more in-your-face class of program insecurities, other, slightly more subtle vulnerabilities are often overlooked during the auditing of source code. One of these classes of vulnerabilities is the "sym link bug", which can indeed be *just* as dangerous as buffer overflow bugs, format string vulnerabilities and heap overflows. Although often misconcepted as "not critical", or "not that serious", it is my belief that symlink bugs can be very serious in nature, and deserve just as much attention as buffer overflow vulnerabilities have received - yet the array of papers regarding this class of security holes is slim.

In this paper, I attempt to demonstrate and analyze the risks of sym link bugs at large, providing interesting case-studies where necessary to demonstrate my points. Information on preventing these sorts of attacks is also provided, with general safe-guards against preventing them.

A knowledge of UNIX-like Operating Systems is assumed, and whilst not necessary it would be helpful to have a working knowledge of C, C++, Perl or BASH scripting.

Symlink bugs: An Overview

So, you might ask, what exactly is a symlink vulnerability? In general, sym link bugs are vulnerabilities which may allow an attacker to overwrite certain or even arbitrary files with the permissions of the invoking user of the vulnerable application or script. Typically, symlink bugs present themselves due to lack of checks on a file, before writing to it. So, to put it simply, symlink bugs exist primarily due to sloppy file handling in an application or script. To make matters even worse, an application or script could be SUID or SGID, thus eliminating the need for a legimate user to invoke the program - the attacker can instead run the application or script herself, because of the privileges the vulnerable application has due to the SUID/SGID pre-set on it. In quite a fair number of cases, a symlink vulnerability exists due to the application writing to tmpfiles, which contain data they may need to use at a later time. Programmers, often enough, may forget to, or not feel the need to perform checks when writing to tmpfiles - this is a common recipe for trouble.

What sort of possibilities for an attacker can symlink bugs actually provide? Well, this usually depends on the type of functionality the vulnerable program was designed to provide. As stated above, the typical symlink bug allows one to overwrite or corrupt files not usually accessible to themselves. At first, this may not seem like a great window of possibility. But, when reconsidered, this could prove promising; what if an attacker could control what was written to an arbitrary file, or could otherwise trick the vulnerable application or script to write to a *critical* system file which could possibly leave the host OS inoperable? In certain circumstances, this possibility *does* present itself, and in others, the attack has less impact.

So, how are symlink vulnerabilities exploited? In most respects, the leverage of symlink attack is actually quite generic across most vulnerable scripts or programs. As touched on above, such bugs usually occur due to poor or lack of file checks before data is written, so a little bit of thinking provides us with the answer: if a vulnerable application or script attempts to write to a file (often tmpfiles, to store data to be used later) without sufficient checks (i.e is the file a symlink? does the file already exist?), an attacker can quickly create a symlink with the *same* name as the file which the application is intending to write to - if the file handling routines are written insecurely enough, the program will obliviously write to the file - *causing the data to be written to where the symlink is pointing*. An attack like this can be demonstrated as below:

root# vulnprog myfile

[...program does some processing...]

k1d$ ln -s /etc/nologin myfile

[...program writes to 'myfile', which points to /etc/nologin...]

In the above example attack scenario, the superuser ran a program with poorly written file handling routines, providing the filename 'myfile' to vulnprog for the relevant data to be written to. However, k1d happened to be looking over the shoulder of 'root' at the time, and created a link from myfile to /etc/nologin.

Although the chances of finding a program written poorly is not at all great, the above scenario is useful for example purposes, and at least illustrates how vulnerable applications are sometimes attacked. Though more critical files could've been overwritten/appended with semi-useless data, the above sample scenario does demonstrate the possibility of attack - if this had been a real life attack, no users would've been able to login, due to the new found existance of /etc/nologin. Thus, one can see why it is important to reveal symlink bugs, and begin to write more secure code.

Although I have only discussed file writing routines being vulnerable to sym link vulnerabilities, other possibilities do arise, such as routines which are written to set privileges. An example scenario would be a SUID root binary which at some point, changes the permissions of a file named 'test' or else to 666. However, the routine does not sufficiently check the state of the file 'test', resulting in a potentially vulnerability. With this, an attacker could create a link from 'test' to /etc/shadow.

A very common occurance of symlink vulnerabilities, perhaps the most common is when applications create tmpfiles insecurely. Not only are the tmpfiles created with predicable names, permissions are not correctly attributed, and the program or script often does not even check if the file is a symlink. This sort of problem is quite a common occurance - this is evident by the vast number of 'symlink bugs' and 'tmpfile bugs' in the bugtraq archive.

In the example discussed above, the attacker could not control what was written to the file, and the program did not have any special privileges (i.e SUID or SGID), so exploitation of the vulnerability required a legimate user to run the application. However, in some cases, the data written *can* be controlled, and, often enough, the application is SUID root. It is obvious to anyone with a minimal amount of security knowledge that this is not good - let us now explore an discuss a classic example of such a vulnerability.

Case Study: Sendmail 8.8.4 Vulnerability

This vulnerability is a classic example, though versions of sendmail vulnerable to this attack are now pretty obsolete. The vulnerability presents itself when the sendmail daemon cannot deliver an email, and thus stores it in the file /var/tmp/dead.letter, incase the email was important to the sender. The sendmail daemon stores the exact email in /var/tmp/dead.letter, exactly how it was written, but what if /var/tmp/dead.letter pointed somewhere? Wouldn't the exact email get written to the file /var/tmp/dead.letter was linked to? Bingo! And since the attacker can write to an arbitrary file (a file of her choice), and also chooses what will be written, this vulnerability is perfect for example purposes. Although /var/tmp/dead.letter must be created as a hard link, rather than a symlink, this is still a link vulnerability, and is of the same class of vulnerabilities, in my opinion.

Example exploit techniques were provided during the discussion of the bug, when it was discovered:

k1d$ ln /etc/passwd /var/tmp/dead.letter
k1d$ nc -v localhost 25
HELO localhost
MAIL FROM: this@host.doesn't.exist
RCPT TO: this@host.doesn't.exist

Sendmail would then attempt to deliver the mail to 'this@host.doesn't.exist', but soon determines that such a recipient does not exist. Due to the design of sendmail, it drops the email message body into /var/tmp/dead.letter. It is due to poor file handling routines that sendmail does not complete sufficient checks on 'dead.letter' in /var/tmp, for possible pitfalls: the possibility of /var/tmp/dead.letter existing as a hard link ('man ln' for more info) to an arbitrary file of interest to an attacker. Instead, sendmail, assumably, just sloppily writes the undelivered email to /var/tmp/dead.letter - this is the manifestation of the vulnerability itself. And since the attacker can exploit this sinister vulnerability to write arbitrary data, privilege escalation is possible - the ultimate goal of an attacker.

Assuming the exploit worked, a new account with the name 'r00t' should exist, with a blank password field, and root privileges. Migiting factors exist which may prevent this vulnerability from existing, such as the account 'postmaster' existing, or /var/tmp being on a different partition, but we shall assume the exploit worked, for example purposes.

kid$ grep "r00t" /etc/passwd

The vulnerability was indeed exploited successfully, let us now look at why sendmail was vulnerable to such an attack in the first place. Several more specific reasons might be given, such as where in the actual code the poorly coded file writing routine exists, and why it is insecure, but the two more general reasons for the issue are outlined below:

- Sendmail is SUID root, thus giving it permission to do most anything.
- Sendmail did not check for the existance of /var/tmp/dead.letter being a hard link - this is due to poor, insecure programming.

Although this provides an excellent example of the possible impacts of sym/hard link vulnerabilities, it still does not provide much of an example of how they manifest in program code. Below we will explore another case study in which we will explore and exploit a sample 'vulnerable script', which is particuarly sloppily written, and in it manifest an obvious symlink vulnerability.

Exploitation: A Vulnerable Script

Below is a sample vulnerable script:

--- ---

if  [ -z '$1' ]; then
        echo 'Usage: vulnscr '

echo 'vulnscr - vulnerable to a symlink bug.'
echo 'writing 'Hello World' to' $1
sleep 3
echo 'Hello World' >> $1
sleep 1
echo 'Setting perms.'
chmod 666 $1
echo 'Done!'
---- EOF ----

Just by studying the commands in the script, it should be quite obvious that this script is vulnerable to a fairly bad symlink vulnerability. '' first checks for the existance of $1 (argument 1), and prints an error message accordingly. A simple information message is printed to the user's terminal, the script sleeps for 3 seconds, and the string 'Hello World' is appended to the file specified at the shell. Then, the permissions of the file are set to 666 (world-writable), and a simple 'Done' message is printed to stdout.

This is simple enough, but as you have most probably noticed, we see no code performing checks on the file given at the command line. Rather, a simple 'echo' command is implemented by our vulnerable script to do its file writing, and what's more, a 'sleep 3' command is ran by our script, heightening even more the possibility of exploitation. On the second from last line of the script, the permissions of the file is set to world-writable, and again, no check is made for a symlink pointing elsewhere.

Below is the offending vulnerable code found in fragment
sleep 3
echo "Hello World" >> $1


chmod 666 $1

By taking a quick glimpse at the above commands, it is soon apparent that no file checks take place - just a blind 'echo' command appending our Hello World string, and a quick 'chmod' invokation. Thus, is definately an avenue for exploitation, and frankly, a recipe for trouble on a corporate or production machine, but in reality, scripts with code as sloppy as this *do* get packaged with major and popular Linux distributions.

Now that we know why it is vulnerable to a classic symlink bug, how could the script be exploited? You can exploit this script by simply creating a symlink to a file writable by the invoking user of Obviously, we would need to know the name of the filename the targetted user specified on the command line, but in a busy workplace environment, this might not be so hard, simply by peering over the shoulder of the person. Another possibility is that the script is to be run as a cronjob, so we know the filename which will be specified. Either way, let's assume we *know* for a fact that a user is about to invoke '', specifying the filename 'test' as the output file.

A simple attack scenario is shown below, illustrating the potential impact of exploitation of this symlink-vulnerable script:

k1d$ ln -s /etc/passwd test


root# test
vulnscr - vulnerable to a symlink bug.
writing 'Hello World' to test
Setting perms.


k1d$ grep -n "Hello World" /etc/passwd
32:Hello World
k1d$ ls -al /etc/passwd
-rw-rw-rw- 1 root root 1460 2004-03-15 16:00 /etc/passwd

So, as you can see, k1d's exploitation of '' worked, and worked extremely well, as illustrated by k1d's checks. "Hello World" is now present in the password file, and to make matters even worse, /etc/passwd is now even world-writable, due to the 'chmod' command written in ''. Chmod followed the symlink 'test', and since the invoking user of was root, the chmod call inevitably succeeded.

So, at this point, k1d has effectively owned the system, and is free to do as he pleases; add root accounts, access mounted devices, corrupt important files and so on. Although it is doubtful that an actual script of this type would appear on a system for real, scripts vulnerable to almost an identical attack do exist in the default install of popular Linux distributions - such as 'extcompose', for example. Extcompose is a small script, packaged with the metamail package. It is designed for allowing a user to make external reference to a file not included in an email. After auditing it for a few short minutes, I realised it was vulnerable to a classic symlink attack, very much similar to the one discussed above. Though it is not SUID root, if an attacker knew what filename a user was going to choose as the output file, she could create a symlink to a file writable by that user - /etc/passwd would be a good choice if the invoking user was root. Due to this vulnerability, important files can be truncated or corrupted, and in theory, privileges could be elevated.

Although you will most likely already have extcompose installed (/usr/bin/extcompose), here is extcompose's code:

--- /usr/bin/extcompose ---
#!/bin/csh -fb
# (The '-fb' might need to be changed to '-f' on some systems)

if ($#argv < 1) then
    echo 'Usage:  extcompose output-file-name'
    exit 1
set OUTFNAME='$1'

echo ''
echo 'Where is the external data that you want this mail message to reference?'
echo '    1 -- In a local file'
echo '    2 -- In an AFS file'
echo '    3 -- In an anonymous FTP directory on the Internet'
echo '    4 -- In an Internet FTP directory that requires a valid login'
echo '    5 -- Under the control of a mail server that will send the data on 
echo ''
echo -n 'Please enter a number from 1 to 5: '
set ans=$<
if ('$ans' == 1)  then
    set accesstype=local-file
else if ('$ans' == 2) then
    set accesstype=afs
else if ('$ans' == 3) then
    set accesstype=anon-ftp
else if ('$ans' == 4) then
    set accesstype=ftp
else if ('$ans' == 5) then
    set accesstype=mail-server
    echo 'That is NOT one of your choices.'
    goto chooseaccesstype
if ('$accesstype' == 'ftp' || '$accesstype' == 'anon-ftp') then
    echo -n 'Enter the full Internet domain name of the FTP site: '
    set site=$<
    echo -n 'Enter the name of the directory containing the file (RETURN for 
top-level): '
    set directory=$<
    echo -n 'Enter the name of the file itself: '
    set name = $<
    echo -n 'Enter the transfer mode (type 'image' for binary data, RETURN 
otherwise): '
    set mode = $<
    if ('$mode' == '') set mode=ascii
    echo 'Content-type: message/external-body; access-type=$accesstype; 
name='\'$name\'\; > '$OUTFNAME'
    echo -n '    site='\'$site\' >> '$OUTFNAME'
    if ('$directory' != '') echo -n '; directory='\'$directory\'>> '$OUTFNAME'
    if ('$mode' != '') echo -n '; mode='\'$mode\'>> '$OUTFNAME'
    echo ''>> '$OUTFNAME'
else if ('$accesstype' == 'local-file' || '$accesstype' == 'afs') then
    echo -n 'Enter the full path name for the file: '
    set name = $<
    if (! -e '$name') then
        echo 'The file $name does not seem to exist.'
        goto fname
    echo 'Content-type: message/external-body; access-type=$accesstype; 
name='\'$name\'> '$OUTFNAME'
else if ('$accesstype' == 'mail-server') then
    echo -n 'Enter the full email address for the mailserver: '
    set server=$<
    echo 'Content-type: message/external-body; access-type=$accesstype; 
server='\'$server\'> '$OUTFNAME'
    echo accesstype '$accesstype' not yet implemented
    goto chooseaccesstype

echo -n 'Please enter the MIME content-type for the externally referenced data: 
set ctype = $<
echo 'Is this data already encoded for email transport?'
echo '  1 -- No, it is not encoded'
echo '  2 -- Yes, it is encoded in base64'
echo '  3 -- Yes, it is encoded in quoted-printable'
echo '  4 -- Yes, it is encoded using uuencode'
set encode=$<
switch ('$encode')
    case 1:
        set cenc=''
    case 2:
        set cenc='base64'
    case 3:
        set cenc='quoted-printable'
    case 4:
        set cenc='x-uue'
        echo 'That is not one of your choices.'
        goto getcenc
echo '' >> '$OUTFNAME'
echo 'Content-type: ' '$ctype' >> '$OUTFNAME'
if ('$cenc' != '') echo 'Content-transfer-encoding: ' '$cenc' >> '$OUTFNAME'
echo '' >> '$OUTFNAME'
if ('$accesstype' == 'mail-server') then
    echo 'Please enter all the data to be sent to the mailserver in the message 
body, '
    echo 'ending with ^D or your usual end-of-data character:'
    cat >> '$OUTFNAME'

I'd like to leave this as an exercise to the reader - figure out why the script is vulnerable, and how it can be exploited.

Additional Thoughts

Although in our examples, exploitation seems ridiculously easy, there are practical considerations to take into account, where theory and practicality are two different things entirely:

- Timing
- Guesswork
- Permission issues

These issues are factors which can effect the likely-hood of exploitation of even gaping symlink vulnerabilities, like the example discussed above.

Depending on where and why the bug manifests in the application's code, timing can be an issue. For example, if an attacker physically spots a work collegue invoking an application with symlink bugs, even if she can find out what filename the application will deal with, will she be fast enough? In an ideal world, this wouldn't be a problem, but in reality, an attacker would've had to have planed for an attack, to a certain extent, because by the time the attacker had created a symlink from the appropriate file name, the vulnerable program could've already terminated execution.

However, this is often not an issue; many applications include various invokations of 'sleep(2)' and 'usleep()'. As noted in the example we discussed previously, delay operations can often greatly increase the attacker's likely-hood of success - during the time a vulnerable application slept, the appropriate conditions could be prepared (i.e creation of a suitable symlink).

Depending on the nature of the program, oftentimes a little guesswork is required by the attacker. A good example of my point is when a file is specified at the shell, upon which file operations are to be performed by a poorly written vulnerable program. Unsurprisingly, many folks prefer filenames which are easy to remember, but not necessarily relevant to what material is stored in the file in question. Many people may just choose a simple filename like 'test'.

In other applications, this is not so much of an issue, due to the fact that filenames are hardcoded into the source code, or are hardcoded to a certain extent - this is often the case for tmpfiles used by many mainstream applications. In this scenario, all an attacker need do is create a symlink bearing the name of the tmpfile which the vulnerable program will operate on (i.e write to, set permissions on it, etc...) to a desirable location (system files, password files, config files etc...).

Permission issues
On an average system, with a small-to-medium user load, system administrators and users in general are not usually over cautious. However, if an attacker does not have write access to either the directory in which files handled by the vulnerable application are created, or the actual file itself, this can become a significant problem for a would-be attacker, as they do not have sufficient privileges to cannot craft a symlink. Coinciding with points stated above, this is often not an issue if an exploitable application writes temporary files in /tmp, but if a user may specify a file, an attack can be thwarted by specifying a path to which attackers do not have sufficient access.

Despite discussions of symlink bugs have been primarily focused towards regular files, tmp directories are vulnerable in almost an identical way. I'll leave it to the reader to delve into the references at the end of the paper.

Prevention & Safe programming

The prevention of symlink bugs, as with all programming, is achieved via good programming standards. In general, symlink vulnerabilities can be avoided in part by employing some of the following techniques.

- Perform checks on files to be handled.
a) Check for existance of file.
b) Check for symlinks
c) Check for hardlinks
d) etc.

This can be done by optionally generating a semi-random filename, and adding the 'O_CREAT|O_EXCL' flags to any 'open()' calls made.

- Implement safe tmpfile creation.

- Give the files restrictive permissions

My aim is not to reinvent the wheel, so instead, references and areas of further reading are given, including measures worth taking to avoid the symlink class of vulnerabilities.


I have attempted to provide papers and material for further reading, which I think may be useful to the reader. - "not-so-dangerous symlink bugs" - a better look. - Preventing Race Conditions.

Large archives such as Bugtraq have quite a big collection of symlink vulnerabilities, checking it out would be interesting.


Thanks for reading. If you have any constructive critisism or comments, I would appreciate your feedback .

Rate this article

All images, content & text (unless other ownership applies) are © copyrighted 2000 -  , All rights reserved. Comments are property of the respective posters.