本文目录导读:

路径遍历漏洞(Path Traversal,又称目录穿越漏洞)是一种常见的Web安全漏洞,攻击者通过操纵文件路径参数,访问或执行服务器上本无权限访问的文件(如 /etc/passwd、敏感配置文件等)。
以下是规避路径遍历漏洞的核心原则与具体实践方法:
核心原则:永远不要信任用户输入
任何来源于用户(URL参数、表单、请求头、Cookie等)的路径信息,都必须被视为不可信的。
具体规避方法
使用白名单机制(最推荐)
只允许用户从预定义的、有限的资源列表中选取,而不是直接让用户输入具体路径或文件名。
- 做法:后端维护一个合法的文件ID列表或文件名映射表,用户传递
id=1,而非file=report.pdf,后端根据id=1去查找对应的物理路径,并进行访问控制。 - 优势:完全杜绝了攻击者构造路径的可能性(如
../../etc/passwd)。
规范化路径并进行严格校验
如果必须接受用户输入的路径(如文件名),可以采用以下步骤:
-
调用系统函数规范化路径:使用内置函数(如 Java 的
File.getCanonicalPath()、Python 的os.path.abspath()、Node.js 的path.resolve())将用户传入的路径转换为绝对路径或规范路径,这一步能自动处理 和 。 -
验证路径的前缀:检查规范化后的路径是否以允许访问的基目录(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-data、nobody)对系统关键目录(如/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 (绝对路径绕过) |
拒绝 |
最有效的方法是组合使用:
- 首选架构层面避免:使用白名单ID或对象存储服务。
- 应用代码层面兜底:如果必须使用用户输入构造路径,务必先规范化再校验路径前缀。
- 系统层面做最后一道防线:最小权限原则 + 沙盒隔离。