Improper Link Resolution Before File Access ('Link Following')

Description

Improper Link Resolution Before File Access, commonly known as "Link Following" or "Symlink Attack," occurs when software accesses a file through a path that can be modified by an attacker who creates or manipulates symbolic links (symlinks), hard links, or junctions. The vulnerability arises when a program follows a symbolic link to access a file without verifying that the link points to an intended and authorized location. Attackers exploit this by creating a symlink that points to a sensitive file (like /etc/passwd or system configuration files), causing the vulnerable application to inadvertently read, write, or delete the targeted file. This is particularly dangerous when the application runs with elevated privileges, as the attacker can leverage those privileges to access files they wouldn't normally have permission to modify.

Risk

Link following vulnerabilities pose severe risks particularly in scenarios where privileged applications handle files in directories writable by unprivileged users. Attackers can redirect file operations to sensitive system files, enabling unauthorized reading of credentials and configuration data, modification of security-critical files, or deletion of important system components. When combined with race conditions (TOCTOU - Time-of-Check-Time-of-Use), these attacks become more reliable and dangerous. Successful exploitation often leads to local privilege escalation, allowing attackers to gain root or SYSTEM-level access. Container escape vulnerabilities frequently leverage symlink attacks to break out of container isolation and access host filesystems.

Solution

Always verify that file paths do not traverse symbolic links when operating in directories writable by untrusted users. Use system calls that operate on file descriptors rather than paths after opening files, and employ flags like O_NOFOLLOW (on Unix) when opening files to prevent following symlinks. Check the type of file before operations using lstat() rather than stat() to detect symbolic links. Create temporary files with unpredictable names in directories with restricted permissions (e.g., using mkstemp()). When deleting files, use unlinkat() with AT_REMOVEDIR flag awareness. On Windows, restrict symbolic link creation permissions and validate junction targets. Implement proper privilege dropping before file operations in directories accessible to lower-privileged users.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Attackers can trick privileged applications into reading sensitive files through symbolic links, exposing credentials, encryption keys, and configuration data.
IntegrityScope: Integrity

File writes intended for one location can be redirected to overwrite critical system files, configuration data, or security settings through malicious symlinks.
AvailabilityScope: Availability

Critical system files or application data can be deleted or corrupted when delete operations follow attacker-controlled symbolic links.
Access ControlScope: Access Control, Privilege Escalation

By manipulating where privileged processes write data, attackers can achieve local privilege escalation to root or SYSTEM level access.

Example Code + Solution Code

The following example demonstrates a vulnerable C program that creates a log file in a world-writable temporary directory:

Vulnerable Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#define LOG_FILE "/tmp/myapp.log"

void write_log(const char *message) {
    FILE *fp;

    // VULNERABLE: No check for symlink before opening
    // Attacker can: ln -s /etc/passwd /tmp/myapp.log
    fp = fopen(LOG_FILE, "a");
    if (fp == NULL) {
        perror("Failed to open log file");
        return;
    }

    fprintf(fp, "%s\n", message);
    fclose(fp);
}

void cleanup_old_log() {
    // VULNERABLE: Follows symlinks when deleting
    // Could delete arbitrary files if symlink exists
    if (access(LOG_FILE, F_OK) == 0) {
        unlink(LOG_FILE);
    }
}

int main() {
    cleanup_old_log();
    write_log("Application started");
    write_log("Performing operation...");
    return 0;
}

An attacker can create a symbolic link before the program runs: ln -s /etc/passwd /tmp/myapp.log. When the privileged application writes to or deletes the log file, it actually modifies /etc/passwd.

Fixed Code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

#define LOG_DIR "/var/log/myapp"
#define LOG_FILE "/var/log/myapp/app.log"

int safe_open_log(const char *filepath) {
    struct stat st;
    int fd;

    // Use O_NOFOLLOW to refuse to follow symlinks
    fd = open(filepath, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, 0640);
    if (fd < 0) {
        if (errno == ELOOP) {
            // File is a symbolic link - reject
            fprintf(stderr, "Error: Log file is a symbolic link\n");
        }
        return -1;
    }

    // Verify the opened file is a regular file using fstat on the fd
    if (fstat(fd, &st) < 0) {
        close(fd);
        return -1;
    }

    if (!S_ISREG(st.st_mode)) {
        fprintf(stderr, "Error: Log file is not a regular file\n");
        close(fd);
        return -1;
    }

    // Verify file is owned by root and has safe permissions
    if (st.st_uid != 0 || (st.st_mode & S_IWOTH)) {
        fprintf(stderr, "Error: Log file has unsafe ownership/permissions\n");
        close(fd);
        return -1;
    }

    return fd;
}

void write_log(const char *message) {
    int fd = safe_open_log(LOG_FILE);
    if (fd < 0) {
        return;
    }

    dprintf(fd, "%s\n", message);
    close(fd);
}

void safe_cleanup_old_log() {
    struct stat lst;

    // Use lstat to check if path is a symlink WITHOUT following it
    if (lstat(LOG_FILE, &lst) < 0) {
        return; // File doesn't exist, nothing to clean
    }

    // Refuse to delete if it's a symbolic link
    if (S_ISLNK(lst.st_mode)) {
        fprintf(stderr, "Error: Will not delete symbolic link\n");
        return;
    }

    // Verify it's a regular file before deletion
    if (!S_ISREG(lst.st_mode)) {
        fprintf(stderr, "Error: Log path is not a regular file\n");
        return;
    }

    // Safe to unlink - but use a dedicated log directory with proper perms
    unlink(LOG_FILE);
}

int main() {
    // Ensure log directory exists with proper permissions
    mkdir(LOG_DIR, 0750);

    safe_cleanup_old_log();
    write_log("Application started");
    write_log("Performing operation...");
    return 0;
}

The fixed code uses O_NOFOLLOW flag to refuse opening symbolic links, lstat() to check file type without following links, fstat() on the file descriptor to verify the actually opened file, and stores logs in a protected directory rather than world-writable /tmp. Additional checks verify file ownership and permissions.


Exploited in the Wild

Docker/Kubernetes Container Escape (Cloud Infrastructure, 2024)

CVE-2024-21626 in runC container runtime allowed container escape through a file descriptor leak that attackers exploited using symlinks to access the host filesystem. Additionally, CVE-2024-23651 demonstrated a symlink race condition during Docker's build process, where attackers could mount sensitive host directories into containers during cache invalidation. These vulnerabilities affected millions of containerized deployments, enabling attackers to escape container isolation and access host systems with elevated privileges.

Nimbuspwn Linux Privilege Escalation (Linux Systems, 2022)

CVE-2022-29799 and CVE-2022-29800 (collectively known as "Nimbuspwn") exploited symlink race conditions in the systemd networkd-dispatcher component to achieve root privilege escalation on Linux systems. Attackers could chain directory traversal with symlink manipulation to execute arbitrary code as root. The vulnerability affected numerous Linux distributions using systemd, demonstrating how symlink attacks remain a significant threat to modern operating systems.

Windows Defender Arbitrary File Deletion (Windows Systems, 2019)

CVE-2019-1161 allowed attackers to exploit Windows Defender through symbolic links to delete arbitrary files with SYSTEM privileges. By creating a junction point combined with an NTFS symbolic link, an unprivileged user could trick Windows Defender into deleting any file on the system during scan operations. Hundreds of millions of Windows machines running Windows 7 and above were vulnerable to this privilege escalation attack.


Tools to test/exploit

  • Symlinkd3 — Google Project Zero's symbolic link testing tools for Windows, including utilities to create mount points, object directories, and test for symlink vulnerabilities.

  • CreateSymlink — utility for creating NTFS symbolic links and junctions on Windows for testing privilege escalation vulnerabilities.

  • LinPEAS — Linux privilege escalation script that checks for symlink-related misconfigurations in temporary directories and writable paths used by privileged processes.


CVE Examples

  • CVE-2024-21626 — runC container runtime file descriptor leak enabling container escape through symlink exploitation.

  • CVE-2022-29799 — Nimbuspwn directory traversal in networkd-dispatcher enabling symlink-based privilege escalation.

  • CVE-2019-1161 — Windows Defender arbitrary file deletion through symbolic link manipulation with SYSTEM privileges.

  • CVE-2015-3629 — Docker libcontainer symlink attack allowing container escape and arbitrary file writes to host.


References

  1. MITRE. "CWE-59: Improper Link Resolution Before File Access ('Link Following')." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/59.html

  2. CyberArk. "Follow the Link: Exploiting Symbolic Links with Ease." Threat Research Blog. https://www.cyberark.com/resources/threat-research-blog/follow-the-link-exploiting-symbolic-links-with-ease

  3. NixHacker. "Understanding and Exploiting Symbolic links in Windows." https://nixhacker.com/understanding-and-exploiting-symbolic-link-in-windows/

  4. Wikipedia. "Symlink race." https://en.wikipedia.org/wiki/Symlink_race