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
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
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
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,
it was discovered:
k1d$ ln /etc/passwd /var/tmp/dead.letter
k1d$ nc -v localhost 25
MAIL FROM: email@example.com't.exist
RCPT TO: firstname.lastname@example.org't.exist
Sendmail would then attempt to deliver the mail to 'email@example.com'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
/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:
--- vulnscr.sh ---
if [ -z '$1' ]; then
echo 'Usage: vulnscr '
echo 'vulnscr - vulnerable to a symlink bug.'
echo 'writing 'Hello World' to' $1
echo 'Hello World' >> $1
echo 'Setting perms.'
chmod 666 $1
---- 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. 'vulnscr.sh' 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 vulnscr.sh:
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, vulnscr.sh 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 vulnscr.sh. 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 'vulnscr.sh', specifying the filename 'test' as the output
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# vulnscr.sh test
vulnscr - vulnerable to a symlink bug.
writing 'Hello World' to test
k1d$ grep -n "Hello World" /etc/passwd
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 'vulnscr.sh' worked, and worked
extremely well, as illustrated by k1d's checks. "Hello World" is now
in the password file, and to make matters even worse, /etc/passwd is now even
world-writable, due to the 'chmod' command written in 'vulnscr.sh'. Chmod
followed the symlink 'test', and since the invoking user of vulnscr.sh was
root, the chmod call inevitably succeeded.
So, at this point, k1d has effectively owned the system, and is free to do
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
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 ---
# (The '-fb' might need to be changed to '-f' on some systems)
if ($#argv < 1) then
echo 'Usage: extcompose output-file-name'
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 -n 'Please enter a number from 1 to 5: '
if ('$ans' == 1) then
else if ('$ans' == 2) then
else if ('$ans' == 3) then
else if ('$ans' == 4) then
else if ('$ans' == 5) then
echo 'That is NOT one of your choices.'
if ('$accesstype' == 'ftp' || '$accesstype' == 'anon-ftp') then
echo -n 'Enter the full Internet domain name of the FTP site: '
echo -n 'Enter the name of the directory containing the file (RETURN for
echo -n 'Enter the name of the file itself: '
set name = $<
echo -n 'Enter the transfer mode (type 'image' for binary data, RETURN
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.'
echo 'Content-type: message/external-body; access-type=$accesstype;
else if ('$accesstype' == 'mail-server') then
echo -n 'Enter the full email address for the mailserver: '
echo 'Content-type: message/external-body; access-type=$accesstype;
echo accesstype '$accesstype' not yet implemented
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'
echo 'That is not one of your choices.'
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
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.
Although in our examples, exploitation seems ridiculously easy, there are
practical considerations to take into account, where theory and practicality
two different things entirely:
- 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...).
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
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
I have attempted to provide papers and material for further reading, which
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 .