flawopen.com/path-traversal/Csharp

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

漏洞深度剖析:Path Traversal in C#

How Path.Combine discards root paths in C#, and how to verify boundaries using Path.GetFullPath and StringComparison.OrdinalIgnoreCase.

💡 通俗易懂的原理解析 (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-csharp.
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: Path.Combine discards baseFolder if userInput has a leading slash
using System.IO;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase {
    private readonly string _baseFolder = @"C:\App\Storage\Uploads";

    [HttpGet("download")]
    public IActionResult Download([FromQuery] string file) {
        // If file is "\Windows\System32\cmd.exe", basePath is discarded!
        // If file is "..\..\App.config", it breaks out of the folder
        string targetPath = Path.Combine(_baseFolder, file);

        if (!System.IO.File.Exists(targetPath)) return NotFound();
        return PhysicalFile(targetPath, "application/octet-stream");
    }
}
✓ 加固后的安全修复
// HARDENED: Canonicalize path and assert directory prefix containment
using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class FileController : ControllerBase {
    private readonly string _baseFolder;

    public FileController() {
        _baseFolder = Path.GetFullPath(@"C:\App\Storage\Uploads");
    }

    [HttpGet("download")]
    public IActionResult Download([FromQuery] string file) {
        if (string.IsNullOrWhiteSpace(file)) return BadRequest("Invalid filename");

        // 1. Strip leading slashes to prevent Path.Combine root discard
        string cleanName = file.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);

        // 2. Resolve canonical full path
        string fullPath = Path.GetFullPath(Path.Combine(_baseFolder, cleanName));

        // 3. Strict boundary containment check with DirectorySeparatorChar
        string safePrefix = _baseFolder.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
        if (!fullPath.StartsWith(safePrefix, StringComparison.OrdinalIgnoreCase)) {
            return Forbid("Path traversal attempt detected");
        }

        if (!System.IO.File.Exists(fullPath)) return NotFound();
        return PhysicalFile(fullPath, "application/octet-stream");
    }
}

工程与系统安全加固清单

References