跳转到帖子

背景

任意文件读取是 Web 应用里比较常见、也容易被低估的一类漏洞。它不像远程命令执行那样直接,但在真实事件里经常成为突破口:读取配置文件、源码、密钥、日志、备份文件后,攻击面会迅速扩大。

这篇文章整理一次 Java Web 项目代码审计中的典型问题。场景做了脱敏和简化,重点放在漏洞成因、审计方法、复现边界和修复思路上,避免涉及对真实系统的攻击利用。

问题场景

项目中有一个文件预览接口,用于给后台用户查看上传后的附件。接口大致逻辑是:前端传入文件名,后端拼接上传目录后读取文件内容并返回。

GET /admin/file/preview?name=report.pdf

从业务上看,这类接口很常见,尤其是后台管理系统、工单系统、知识库系统中。但如果文件名参数没有做严格约束,就容易出现路径穿越,最终导致任意文件读取。

问题代码示例

审计时看到的代码结构大致如下:

String baseDir = uploadConfig.getBaseDir();
String name = request.getParameter("name");
File file = new File(baseDir + File.separator + name);

if (!file.exists()) {
    response.setStatus(404);
    return;
}

try (InputStream in = new FileInputStream(file)) {
    IOUtils.copy(in, response.getOutputStream());
}

这段代码的主要问题不在于使用了 FileInputStream,而在于直接信任了用户输入的 name 参数。只要参数中包含路径跳转符号,就可能脱离预期的上传目录。

技术分析

路径穿越漏洞的核心点是:应用以为自己访问的是某个受控目录下的文件,但操作系统在解析路径后,实际访问的可能是受控目录之外的文件。

例如,应用上传目录为:

/data/app/uploads

如果代码直接拼接用户输入:

/data/app/uploads/../config/application.yml

操作系统规范化路径后,实际访问路径可能变成:

/data/app/config/application.yml

如果缺少边界校验,应用层很难意识到访问已经越界。

审计关注点

做代码审计时,我一般会优先检索以下几类代码:

new File(
FileInputStream
Files.readAllBytes
Files.copy
Paths.get
ResourceUtils.getFile
response.getOutputStream

之后重点看这些 API 的路径参数来源。如果路径参数来自以下位置,就需要进一步确认是否做了安全处理:

  • HTTP 请求参数,如 name、path、file、url。
  • 请求头,如导出、下载、预览相关的自定义头。
  • 数据库字段,尤其是用户可提交、可编辑的附件路径。
  • 消息队列或异步任务参数。
  • 压缩包解压后的文件名。

仅检查是否包含 ../ 并不可靠。不同系统、不同编码方式、不同路径分隔符都会带来绕过风险。更稳妥的方式是做路径规范化后再判断边界。

安全复现边界

在测试环境验证时,不建议去读取系统敏感文件。更合适的做法是在受控环境里放置一个标记文件,用于判断是否能越过上传目录。

例如测试目录结构如下:

/tmp/demo/
├── uploads/
│   └── normal.txt
└── marker.txt

接口预期只能读取 /tmp/demo/uploads 下的文件。如果通过参数能读取到 /tmp/demo/marker.txt,就可以证明存在越权读取风险。

GET /admin/file/preview?name=../marker.txt

这里的验证方式只针对本地或授权测试环境,不涉及真实业务系统的敏感文件,也便于后续写入测试报告和修复验证。

常见错误修复方式

这类漏洞的修复经常会出现一些看似有效、实际不稳的方案。

  • 只用字符串判断 contains("../"),容易被编码、不同分隔符、重复规范化绕过。
  • 只限制文件后缀,如只允许 .pdf,但路径仍可能越界。
  • 只判断文件是否存在,不判断是否位于允许目录内。
  • 将用户传入路径直接存入数据库,后续读取时默认可信。
  • 使用黑名单过滤敏感目录,维护成本高且容易遗漏。

推荐修复思路

比较可靠的修复方式是:固定基准目录,对用户输入做白名单约束,并在路径规范化后判断是否仍在基准目录内。

Path basePath = Paths.get(uploadConfig.getBaseDir()).toRealPath();
String name = request.getParameter("name");

if (name == null || !name.matches("^[a-zA-Z0-9._-]{1,100}$")) {
    response.setStatus(400);
    return;
}

Path targetPath = basePath.resolve(name).normalize();

if (!targetPath.startsWith(basePath)) {
    response.setStatus(403);
    return;
}

if (!Files.isRegularFile(targetPath)) {
    response.setStatus(404);
    return;
}

try (InputStream in = Files.newInputStream(targetPath)) {
    in.transferTo(response.getOutputStream());
}

上面示例里有几个关键点:

  • basePath 使用 toRealPath() 获取真实路径,减少符号链接等因素带来的误判。
  • 文件名使用白名单,只允许简单文件名,而不是任意路径。
  • resolve 后调用 normalize,再用 startsWith 判断是否仍在基准目录下。
  • 使用 Files.isRegularFile,避免读取目录、设备文件等非普通文件。

更稳的业务设计

如果业务允许,建议不要让前端直接传文件路径或文件名,而是传文件 ID。后端根据 ID 查询数据库中的附件记录,再拼接服务端保存的相对文件名。

GET /admin/file/preview?id=1024

服务端流程可以调整为:

  • 根据当前用户和文件 ID 查询附件记录。
  • 确认该用户是否有权限访问该附件。
  • 从数据库中取服务端生成的存储名,而不是使用用户提交的路径。
  • 按固定目录读取文件,并进行路径边界检查。

这种方式能同时解决两个问题:路径穿越和水平越权。很多文件读取接口表面是路径问题,深入看其实还伴随权限边界不清。

日志与检测建议

修复之外,建议对异常路径访问做基础审计。比如出现以下特征时记录安全日志:

  • 参数中包含 ..、/、\ 等路径符号。
  • 出现 URL 编码后的路径跳转字符。
  • 访问不存在文件的频率明显异常。
  • 同一账号短时间内访问大量不同文件名。

日志不要记录完整敏感路径和文件内容,重点记录用户、接口、参数摘要、客户端地址、拒绝原因即可。

log.warn("Blocked suspicious file access, userId={}, api={}, reason={}, nameHash={}",
        userId, "/admin/file/preview", "path_boundary_check_failed", sha256(name));

代码审计清单

检查项说明
路径参数来源确认文件路径是否来自用户输入、数据库或外部系统
是否使用白名单优先允许简单文件名或文件 ID,避免任意路径
是否规范化路径使用 normalize、toRealPath 等方式处理路径
是否校验目录边界读取前判断目标路径是否仍在允许目录内
是否校验权限确认当前用户是否有权访问目标文件
是否限制文件类型结合业务限制后缀、MIME、大小等属性
是否记录异常访问对疑似探测行为进行安全审计

注意点

  • Windows 和 Linux 的路径分隔符不同,审计时不要只考虑一种系统。
  • 压缩包解压也会遇到类似问题,俗称 Zip Slip,需要对压缩包内文件名做同样的边界检查。
  • 如果上传目录中允许符号链接,需要特别谨慎,避免链接指向目录外。
  • 下载、预览、导出、模板读取、日志查看接口都属于高频风险点。
  • 修复后要补充单元测试,覆盖正常文件名、路径跳转、空值、超长文件名、特殊字符等情况。

总结

任意文件读取漏洞的根因通常不是某个 API 本身危险,而是路径边界没有定义清楚。只要接口允许用户影响文件路径,就应该默认存在风险,并通过白名单、路径规范化、目录边界校验和权限控制来收敛。

在代码审计中,这类问题值得长期关注。它复现成本低、影响面容易扩大,且经常隐藏在后台功能、运维功能和文件预览功能里。修复时不要停留在过滤字符串,应该从业务设计和路径模型上把边界收紧。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…