flawopen.com/path-traversal/C-cpp

● CWE-918 · 高危
安全研究 · FlawOpen

漏洞深度剖析:Path Traversal in C/C++

How snprintf file path construction allows directory escapes in C/C++, and how to enforce containment using realpath() and strncmp.

💡 通俗易懂的原理解析 (ELI5)

设想一家酒店的房卡扫描锁原本只允许开启二楼的客房。如果客人在门禁键盘上输入 '../../master-safe',有缺陷的门锁就会跳出当前楼道,直接打开酒店经理办公室的主保险箱。

核心概念与专有名词

Web Application Security
技术概念 (Web Application Security):Core architecture component affected by CWE-918.
CWE-918
技术概念 (CWE-918):Standard Common Weakness Enumeration classification for path-traversal-c-cpp.
Defense-in-Depth
技术概念 (Defense-in-Depth):Multi-layered engineering verification and runtime boundary isolation.

攻击执行流程分解

Step 1

客户端提交文件路径参数

应用程序接口通过 HTTP 参数接收用户指定的文件名、报告路径或静态资源标识。

Step 2

目录遍历序列注入

攻击者在文件名参数中注入相对路径遍历符号(如 '../', '..%2f')或绝对路径覆盖。

Step 3

文件系统根目录边界逃逸

后端直接将不可信输入与基础目录拼接,未进行规范化绝对路径解析(Canonicalization)与边界校验。

Step 4

敏感文件未授权读取或覆盖

系统运行时打开并回传敏感配置文件(如 /etc/passwd、源码凭证、环境变量密钥),造成数据外泄。

源代码对比:漏洞与安全实现

✕ 存在漏洞的实现
// VULNERABLE: Direct snprintf and open without realpath check
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int serve_user_file(const char *user_filename) {
    char full_path[1024];
    const char *base_dir = "/var/app/public/files";

    // Attacker input: "../../../../etc/shadow"
    snprintf(full_path, sizeof(full_path), "%s/%s", base_dir, user_filename);

    // Directly opens system shadow file!
    int fd = open(full_path, O_RDONLY);
    return fd;
}
✓ 加固后的安全修复
// HARDENED: Canonicalize with realpath and verify directory prefix bounds
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>

int serve_user_file(const char *user_filename) {
    const char *base_dir = "/var/app/public/files";
    char base_canonical[PATH_MAX];
    char raw_path[PATH_MAX];
    char target_canonical[PATH_MAX];

    // 1. Resolve canonical base directory
    if (realpath(base_dir, base_canonical) == NULL) {
        return -1;
    }

    // 2. Safely construct raw path
    if (snprintf(raw_path, sizeof(raw_path), "%s/%s", base_canonical, user_filename) >= (int)sizeof(raw_path)) {
        return -1; // Path truncated
    }

    // 3. Resolve canonical target path
    if (realpath(raw_path, target_canonical) == NULL) {
        return -1; // File does not exist or access error
    }

    // 4. Verify canonical target begins with base_canonical + '/'
    size_t base_len = strlen(base_canonical);
    if (strncmp(target_canonical, base_canonical, base_len) != 0 || 
        target_canonical[base_len] != '/') {
        return -1; // Traversal detected!
    }

    return open(target_canonical, O_RDONLY | O_CLOEXEC);
}

工程与系统安全加固清单

References