Lab: Path Traversal Attack
This lab will show you a common security flaw called “Path Traversal.” We’ll start by exploiting a vulnerable program ( The You can verify the SUID executable This file is owned by the We’ll run the program to read The First, let’s see how the program is supposed to work. We’ll run it to read Now, let’s try something malicious. We will authenticate as Why did the attack succeed? The program made a critical mistake: Now, let’s test Attempt the Same Attack: We’ll repeat the exact same attack, this time targeting The attack failed because This principle is simple but powerful: only use elevated privileges for the shortest time necessary, and reduce them as soon as the high-privilege task is done. Let’s explore PTT’s main daemon program: mbbsd.c and check if it’s secure. In the following code, Even if the attacker finds a vulnerability that leads to memory corruption bug (e.g. buffer overflow) and can overwrite readmail
) to read another user’s private mail. Then, we’ll examine a secure version (readmail-safe
) to understand how to fix this vulnerability using the “Principle of Least Privilege.”Setup
path-traversal-attack
directory.setup.sh
script creates two users (user1
, user2
) and sets up their password and mail files for our experiment. It also compiles the readmail
and readmail-safe
executables and install them to /usr/local/bin
and setuid
on them.# Run the setup script with root privileges
cd path-traversal-attack
sudo bash setup.sh
Experiment 1: Path Traversal
readmail
program is a “SUID root” executable. This means that even though we’ll run it as a regular user, the operating system will grant it the permissions of its owner, which is root. The program needs this temporary power to read the system-wide password file for authentication.readmail
with ls -l
:$ ls -l $(which readmail)
-rwsr-xr-x 1 root root 84224 Sep 16 08:31 /usr/local/bin/readmail
root
user. The s
in the permission string is the SUID bit. It tells the kernel: “When anyone executes this file, run the process as the file’s owner (root
), not as the person who typed the command.”Normal Usage
mail1
for user1
. Since setup.sh
installed it in /usr/local/bin
, (try: echo $PATH
and see if /usr/local/bin
is in it), you can call it directly without typing the full path: /usr/local/bin/readmail
.# We're running as the regular 'vscode' user, with no 'sudo'
$ readmail user1 mail1
Password: pass1
# Expected output: The content of user1's mail1.
readmail
program runs with the permissions of its owner (root
), not the user who executes it, in order to read the system password file.mail1
for user1
.# The first argument is the username, the second is the mail file.
$ readmail user1 mail1
Password: pass1
# Expected output: The content of user1's mail1.
Malicious usage
user1
, but ask the program to read a file at the path ../user2/mail1
. The ..
tells the system to go up one directory level.$ readmail user1 ../user2/mail1
Password: pass1
# Success! We can now read the contents of user2's private mail...
root
.user1
’s password.root
privileges.../user2/mail1
) and tries to open that file.root
. Since root
can read anything, the OS allows it.Experiment 2: The Principle of Least Privilege
readmail-safe
, the fixed version of the program.readmail-safe
.$ readmail-safe user1 ../user2/mail1
Password: pass1
# Expected output: "Permission denied" or a similar error. The attack fails!
How It Works
readmail-safe
follows the Principle of Least Privilege. Here’s its improved logic:root
.user1
’s password.seteuid()
system call to drop its privileges, changing its EUID from root
to the authenticated user (user1
).../user2/mail1
, it’s no longer acting as root
. It’s acting as user1
.user1
is trying to access user2
’s private files and correctly denies permission.Food for Thought
seteuid()
to drop privileges. What’s the difference between seteuid()
and setuid()
in Linux?readmail
to exploit it?Bonus
PTT daemon starts as root (so it can run privileged operations like opening network ports or creating shared memory, etc.). Then, before accepting any real user sessions, it call setuid(BBSUID)
to drop to the unprivileged bbs
user. Then, every new logged-in user runs in separate process but all with the same UID. Unlike our readmail-safe
, PTT does not rely on the OS to ensure one user cannot read another user’s private file. PTT uses its built-in application enforcement. Similar to us, user files are also constructed using snprintf
in common/bbs/path.c, but it is validated with is_validuserid
in common/bbs/names.c.userid
is validated everytime PTT constructs the filepath.void sethomepath(char *buf, const char *userid) {
(is_validuserid(userid));
assert(buf, PATHLEN, "home/%c/%s", userid[0], userid);
snprintf}
userid
into something like ../../other_user/other_user
, the path will be rejected by the assert(is_validuserid(userid));
. It is quite difficult to construct /home/bbs/home/my_user/my_user/../../other_user/other_user
(attacker tries to read /home/bbs/home/other_user/other_user
) without being caught.