看我如何突破JFinal黑名单机制实现任意文件上传
字数 1045 2025-08-18 11:38:52
JFinal框架黑名单绕过实现任意文件上传漏洞分析与防御
漏洞概述
JFinal是一款优秀的国产Java Web框架,在处理文件上传功能时存在黑名单机制被绕过的风险,可能导致攻击者上传恶意文件(如JSP木马)到服务器。该漏洞由平安银行应用安全团队发现并报告,厂商已修复。
漏洞原理
JFinal文件上传机制
JFinal框架处理文件上传的核心流程:
- 文件接收阶段:使用
com.oreilly.servlet.MultipartRequest类处理上传请求 - 黑名单检查阶段:通过
isSafeFile(UploadFile uploadFile)方法进行后缀名检查
关键代码分析
黑名单检查函数
isSafeFile(UploadFile uploadFile) {
String fileName = uploadFile.getFileName().trim().toLowerCase();
if (!fileName.endsWith(".jsp") && !fileName.endsWith(".jspx")) {
return true;
}
uploadFile.getFile().delete(); // 删除不合规文件
return false;
}
文件上传处理流程
// 1. 接收上传文件
this.multipartRequest = new com.oreilly.servlet.MultipartRequest(
request, uploadPath, maxPostSize, encoding, fileRenamePolicy);
// 2. 遍历上传的文件
Enumeration files = this.multipartRequest.getFileNames();
while(files.hasMoreElements()) {
String name = (String)files.nextElement();
String filesystemName = this.multipartRequest.getFilesystemName(name);
if(filesystemName != null) {
// 3. 对每个文件进行黑名单检查
UploadFile uploadFile = new UploadFile(name, uploadPath, filesystemName,
this.multipartRequest.getOriginalFileName(name),
this.multipartRequest.getContentType(name));
if(this.isSafeFile(uploadFile)) {
this.uploadFiles.add(uploadFile);
}
}
}
漏洞根本原因
JFinal框架的文件上传机制存在设计缺陷:
- 先上传后检查:所有文件都会先被上传到服务器,然后再进行黑名单检查
- 异常处理不当:如果在黑名单检查前触发异常,程序会终止执行,但文件已上传成功
漏洞复现
攻击方法
- 构造畸形请求:在正常文件参数后添加不存在的参数
- 边界不完整:在请求末尾不添加完整的分隔符(boundary)
POC示例
POST /upload HTTP/1.1
Host: target.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary12345
------WebKitFormBoundary12345
Content-Disposition: form-data; name="file"; filename="test.jsp"
Content-Type: text/plain
<% out.println("Hello JSP"); %>
------WebKitFormBoundary12345
Content-Disposition: form-data; name="nonexistparam" # 不存在的参数
攻击效果
- 第一个文件参数
test.jsp会被正常上传 - 处理第二个参数
nonexistparam时会抛出异常 - 程序终止执行,跳过黑名单检查
test.jsp文件保留在服务器上
其他可能的攻击方式
-
资源竞争攻击:
- 上传一个会写入木马的JSP文件
- 同时疯狂访问该上传路径
- 可能在文件被删除前访问到JSP并执行成功
-
大小写/特殊字符绕过:
- 使用
.Jsp、.jsp%00等变形后缀 - 取决于具体实现是否规范化处理文件名
- 使用
修复建议
开发层面
- 白名单机制:使用白名单而非黑名单,只允许特定安全后缀
- 先检查后上传:在文件保存到磁盘前完成所有安全检查
- 文件名规范化:统一处理大小写、去除特殊字符等
- 内容检查:检查文件内容是否符合预期类型
代码示例(修复后)
// 1. 定义允许的文件类型白名单
private static final Set<String> ALLOWED_EXTENSIONS =
Set.of("jpg", "png", "gif", "pdf", "txt");
public void upload() {
UploadFile file = getFile("file");
// 2. 检查文件扩展名
String fileName = file.getFileName();
String ext = getFileExtension(fileName).toLowerCase();
if (!ALLOWED_EXTENSIONS.contains(ext)) {
file.getFile().delete();
renderError("不允许的文件类型");
return;
}
// 3. 其他安全检查...
// 4. 处理文件
// ...
}
private String getFileExtension(String filename) {
return filename.substring(filename.lastIndexOf(".") + 1);
}
运维层面
- 上传目录隔离:将上传目录设置为不可执行
- 权限控制:上传目录设置适当的读写权限
- 文件重命名:使用随机生成的文件名,避免直接使用用户提供的文件名
- 定期扫描:对上传目录进行定期安全扫描
总结
该漏洞揭示了文件上传功能实现中的常见问题:
- 安全检查和文件保存的顺序至关重要
- 异常处理流程可能引入安全风险
- 黑名单机制本质上是不安全的
开发者应遵循"先验证,后处理"的原则,采用白名单机制,并对上传文件进行多重安全检查,才能有效防范此类漏洞。