路径遍历漏洞如何规避

wen 开源项目 26

本文目录导读:

路径遍历漏洞如何规避

  1. 核心原则:永远不要信任用户输入
  2. 具体规避方法
  3. 示例代码:不安全 vs 安全
  4. 测试与验证清单

路径遍历漏洞(Path Traversal,又称目录穿越漏洞)是一种常见的Web安全漏洞,攻击者通过操纵文件路径参数,访问或执行服务器上本无权限访问的文件(如 /etc/passwd、敏感配置文件等)。

以下是规避路径遍历漏洞的核心原则与具体实践方法

核心原则:永远不要信任用户输入

任何来源于用户(URL参数、表单、请求头、Cookie等)的路径信息,都必须被视为不可信的。


具体规避方法

使用白名单机制(最推荐)

只允许用户从预定义的、有限的资源列表中选取,而不是直接让用户输入具体路径或文件名。

  • 做法:后端维护一个合法的文件ID列表或文件名映射表,用户传递 id=1,而非 file=report.pdf,后端根据 id=1 去查找对应的物理路径,并进行访问控制。
  • 优势:完全杜绝了攻击者构造路径的可能性(如 ../../etc/passwd)。

规范化路径并进行严格校验

如果必须接受用户输入的路径(如文件名),可以采用以下步骤:

  1. 调用系统函数规范化路径:使用内置函数(如 Java 的 File.getCanonicalPath()、Python 的 os.path.abspath()、Node.js 的 path.resolve())将用户传入的路径转换为绝对路径或规范路径,这一步能自动处理 和 。

  2. 验证路径的前缀:检查规范化后的路径是否以允许访问的基目录(Base Directory)开头。

    • 示例(Java):

      File file = new File(BASE_DIRECTORY, userInput);
      String canonicalPath = file.getCanonicalPath(); // 将 ../../etc/passwd 转换为 /etc/passwd
      // 关键检查:是否仍在允许的目录内?
      if (!canonicalPath.startsWith(BASE_DIRECTORY)) {
          throw new SecurityException("Access Denied");
      }
    • 需要注意的是:仅进行 startsWith 检查有时仍不安全(/safe/../etc 的规范化),务必先规范化,再检查

拒绝并过滤危险字符

可以禁止包含以下特征字符的输入,但此方法不能作为唯一防御,因为编码绕过(如URL编码 %2e%2e%2f)或双重编码可能奏效。

  • (双点)
  • 或 (路径分隔符,不同系统不同)
  • 空字节 %00 (用于绕过一些旧系统的字符串长度检查)

推荐做法:不仅过滤这些字符,应直接拒绝包含这些字符的请求。

使用沙盒或隔离的文件存储服务

将用户上传或访问的文件,存储在与应用代码逻辑分离的位置,

  • 对象存储(如AWS S3、阿里云OSS、MinIO):通过预签名URL授权访问,应用本身不直接处理文件系统路径。
  • 专用文件服务器:应用只提供文件ID,由另一个服务根据ID从隔离的存储区域读取。
  • 数据库BLOB:将小文件直接存储在数据库的二进制字段中,从根源上消除文件系统路径。

限制Web Server的权限

即使出现漏洞,也能通过操作系统层面的限制减少损失。

  • 运行用户最小权限:确保Web应用运行用户(如 www-datanobody)对系统关键目录(如 /etc//var/log/只有读权限(最好完全没有),对Web根目录下的文件只读
  • 使用Chroot或容器:将Web应用运行在轻量级隔离环境(如Docker)或chroot jail中,限制其对宿主机文件系统的访问。
  • 禁用不必要的功能:在应用框架或Web服务器(如Apache、Nginx)中,禁用目录列表、符号链接跟随等功能。

示例代码:不安全 vs 安全

不安全(Java)

String fileName = request.getParameter("file");
// 危险:直接拼接用户输入
File file = new File("/var/www/files/" + fileName);
FileInputStream fis = new FileInputStream(file);
// 攻击者可传入 ../../etc/passwd

相对安全(Java)

private static final String UPLOAD_DIR = "/var/www/files/";
public void serveFile(String userInput) {
    // 1. 首先拒绝明显有害的输入(过滤层,非主防御)
    if (userInput.contains("..") || userInput.contains("/") || userInput.contains("\\")) {
        throw new SecurityException("Invalid file name");
    }
    // 2. 使用规范路径(最终防御)
    File file = new File(UPLOAD_DIR, userInput);
    String canonicalPath = file.getCanonicalPath();
    // 3. 强制验证路径前缀
    if (!canonicalPath.startsWith(UPLOAD_DIR)) {
        throw new SecurityException("Access Denied");
    }
    // 4. (可选) 确认文件存在且是普通文件
    if (!file.isFile()) {
        throw new FileNotFoundException("File not found");
    }
    // 安全地读取文件
}

测试与验证清单

部署前可尝试以下测试用例来验证修复效果:

测试用例 期望结果
../../etc/passwd 拒绝
../../windows/system32/config/sam 拒绝
%2e%2e%2f%2e%2e%2fetc/passwd (URL编码) 拒绝
..%252f..%252f..%252fetc/passwd (双重编码) 拒绝
....//....//etc/passwd (绕过简单过滤) 拒绝
..\..\..\Windows\System32\drivers\etc\hosts (Windows路径) 拒绝
/etc/passwd (绝对路径绕过) 拒绝

最有效的方法是组合使用:

  1. 首选架构层面避免:使用白名单ID或对象存储服务。
  2. 应用代码层面兜底:如果必须使用用户输入构造路径,务必先规范化校验路径前缀
  3. 系统层面做最后一道防线:最小权限原则 + 沙盒隔离。

抱歉,评论功能暂时关闭!