跳转到帖子

背景文件上传功能在业务系统里很常见,比如头像、附件、工单材料、富文本图片等。它看起来只是一个普通功能,但在真实代码审计和安全测试中,上传点经常是高风险入口:一旦校验不严,可能导致任意文件写入、脚本执行、存储型 XSS、敏感文件覆盖,甚至成为后续攻击链的起点。这篇文章不讨论如何利用漏洞取得非法权限,而是从防守视角整理一套比较实用的分析方法:如何在代码审计中识别风险点,如何在授权环境里做安全验证,以及如何把上传模块加固到相对可靠的状态。典型场景假设一个 Web 系统提供附件上传接口,后端逻辑大致如下:POST /api/upload

参数:
file: 上传文件
type: avatar / ticket / doc常见实现流程一般包括:前端限制文件后缀和大小;后端接收 multipart/form-data;读取原始文件名;拼接保存路径;保存到 Web 可访问目录;返回文件 URL 给前端展示。如果这几个环节只做了浅层校验,就很容易出现问题。实际审计时,我一般会重点看三个方面:文件类型校验、保存路径处理、访问与执行权限控制。问题一:只信任前端校验不少系统会在前端写类似限制:if (!file.name.endsWith('.jpg') && !file.name.endsWith('.png')) {
alert('只允许上传图片');
}这种校验只能改善用户体验,不能作为安全边界。HTTP 请求可以被正常构造,后端必须独立校验。后端如果只是简单判断文件名后缀,也不够稳妥。例如:String filename = file.getOriginalFilename();

if (filename.endsWith(".jpg") || filename.endsWith(".png")) {
file.transferTo(new File(uploadDir + filename));
}这里至少存在几个隐患:文件名由用户控制,可能包含特殊字符;后缀判断大小写不一致时容易漏判;没有校验真实文件内容;保存路径直接拼接,可能引入路径穿越风险;上传目录如果可执行脚本,风险会被放大。问题二:Content-Type 不能作为唯一依据很多上传接口会检查 Content-Type,例如只允许 image/jpeg、image/png。但这个字段来自客户端请求头,同样不能完全信任。Content-Disposition: form-data; name="file"; filename="test.jpg"
Content-Type: image/jpeg更稳妥的做法是组合校验:白名单后缀校验;MIME 类型校验;文件魔数校验;图片类文件可尝试解码并重新编码;限制文件大小、尺寸、数量和上传频率。以图片上传为例,除了判断后缀,还可以读取文件头部魔数。常见格式示例:类型常见魔数JPEGFF D8 FFPNG89 50 4E 47 0D 0A 1A 0AGIF47 49 46 38PDF25 50 44 46不过需要注意,魔数校验也不是万能的。对于图片场景,更推荐上传后使用服务端图像库重新处理,生成新的图片文件,这样可以去除部分异常数据和嵌入内容。问题三:文件名和路径处理不当文件上传漏洞里,路径处理是非常常见的风险点。审计时可以重点搜索这些代码特征:getOriginalFilename()
transferTo()
MultipartFile
move_uploaded_file
Request.Files
saveAs
FileOutputStream
Path.Combine
uploadDir + filename危险写法通常是把用户传入的文件名直接拼接到保存路径:String filename = file.getOriginalFilename();
File target = new File(basePath + "/" + filename);
file.transferTo(target);更建议的方式是:服务端生成随机文件名,原始文件名只作为展示字段保存到数据库,不参与真实落盘路径。String ext = getSafeExtension(originalFilename);
String safeName = UUID.randomUUID().toString().replace("-", "") + "." + ext;

Path uploadRoot = Paths.get("/data/app/uploads").toAbsolutePath().normalize();
Path target = uploadRoot.resolve(safeName).normalize();

if (!target.startsWith(uploadRoot)) {
throw new SecurityException("invalid upload path");
}

Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);这里有几个关键点:使用固定上传根目录;保存前对路径 normalize;判断最终路径必须仍在上传目录内;文件名由服务端生成;扩展名来自白名单解析,不直接信任原始文件名。问题四:上传目录可执行即使文件类型校验存在缺陷,如果上传目录不可执行,风险也会降低很多。反过来,如果上传目录位于 Web 根目录下,并且服务器会解析其中的脚本文件,一旦校验被绕过,影响面会明显扩大。推荐做法:上传文件存储在 Web 根目录之外;通过下载接口读取文件并返回,而不是直接暴露真实路径;对象存储桶设置合理的访问策略;上传目录禁用脚本执行;响应时设置安全的 Content-Type 与 Content-Disposition。Nginx 中可以对上传目录做静态访问限制,示例:location /uploads/ {
root /data/app;
types {}
default_type application/octet-stream;
add_header X-Content-Type-Options nosniff;
}

location ~* ^/uploads/.*\\.(php|jsp|jspx|asp|aspx|sh|pl|py)$ {
deny all;
}如果业务不需要浏览器直接渲染上传内容,可以统一以附件方式下载:Content-Disposition: attachment; filename="safe-name.jpg"
X-Content-Type-Options: nosniff这对降低存储型 XSS、HTML 文件直接执行等问题也有帮助。授权验证思路在企业内部安全测试或代码审计复现时,建议按“最小影响”的方式验证,不要上传危险脚本或影响业务的数据。可以准备几类无害样本:正常图片文件,用于验证基础功能;超大文件,用于验证大小限制;错误后缀文件,用于验证白名单;后缀大小写变化文件,用于验证匹配逻辑;文件名包含空格、中文、特殊符号的样本,用于验证兼容性;伪造 Content-Type 的普通文本文件,用于验证服务端是否只信任请求头。例如可以用 curl 构造不同类型的上传请求,观察服务端返回和落盘结果:curl -i -X POST \\
-F "type=avatar" \\
-F "[email protected];type=image/jpeg"验证重点不是“能不能执行”,而是确认服务端是否做到了:拒绝非白名单类型;拒绝异常大小文件;不使用用户原始文件名落盘;最终路径没有逃逸上传目录;返回 URL 不泄露服务器真实路径;上传目录不可执行;下载响应头符合预期。代码审计检查清单整理一个比较实用的检查清单,适合审计时逐项确认:检查项风险点建议后缀校验黑名单容易遗漏使用白名单,并统一小写处理文件名处理路径穿越、覆盖文件服务端生成随机文件名保存路径目录逃逸normalize 后校验 startsWith类型判断伪造 Content-Type组合 MIME、魔数、解码校验上传目录脚本解析执行放在 Web 根目录外,禁用执行访问控制越权下载下载接口校验权限响应头浏览器嗅探执行设置 nosniff 和 attachment日志审计问题难追踪记录用户、文件哈希、大小、类型加固实现建议比较稳妥的上传流程可以设计成这样:接口层限制请求大小;业务层按上传场景定义允许类型;读取少量文件头进行格式识别;对图片执行解码和重新编码;生成随机文件名和分级目录;文件存储到非 Web 根目录或对象存储;数据库保存文件元信息,包括原始文件名、存储名、大小、哈希、上传人;下载时走鉴权接口;定期清理无引用临时文件。文件哈希也很有用,可以辅助去重、审计和排查:MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(fileBytes);
String sha256 = HexFormat.of().formatHex(hash);对于高风险业务,还可以接入异步扫描流程:上传后先进入隔离区,扫描完成后再变更为可访问状态。这样即使检测链路有延迟,也不会直接暴露未经检查的内容。常见误区只靠前端限制:前端校验不能作为安全边界。只看后缀名:后缀可以伪造,应组合多种校验。上传目录直接暴露:建议通过受控下载接口访问。忽略权限控制:附件 URL 如果可被枚举,可能导致敏感信息泄露。错误信息过细:不要把服务器路径、框架异常直接返回给用户。只修单点:上传链路涉及网关、应用、存储、Web 服务配置,需要整体加固。事件复盘时需要关注什么如果已经发现上传相关风险,复盘时建议收集以下信息:异常上传时间窗口;上传接口访问日志;上传用户、IP、User-Agent;落盘文件列表、哈希、大小、创建时间;Web 访问日志中是否存在异常访问上传文件的记录;服务器是否出现异常进程、异常计划任务或可疑外连;是否存在同类上传接口使用了相同代码。复盘不要只删除异常文件,还要确认入口是否已修复、权限是否收敛、日志是否完整。同一套上传组件往往会被多个业务复用,建议横向排查。总结文件上传安全的核心不是某一个校验点,而是多层防护:输入校验、路径安全、存储隔离、执行权限、访问控制和审计日志都要覆盖。实际审计中,如果看到“原始文件名直接拼接路径”“上传目录可被 Web 服务器解析”“只靠 Content-Type 判断”这几类特征,就需要重点关注。比较推荐的落地原则是:用户只能决定上传内容,不能决定保存路径、真实文件名和执行方式。把上传文件当作不可信输入处理,再配合隔离存储和受控访问,才能把这类风险控制在可接受范围内。

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

0篇意见

推荐意见

没有意见。

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