【Java代码审计】文件上传篇
- 1.Java常见文件上传方式
- 2.文件上传漏洞修复
1.Java常见文件上传方式
1、通过文件流的方式上传
public static void uploadFile(String targetURL, String filePath) throws IOException {
File file = new File(filePath);
FileInputStream fileInputStream = new FileInputStream(file);
URL url = new URL(targetURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setRequestMethod("POST");
// 设置请求头,根据实际需求进行更改
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=---boundary");
// 创建文件流输出流
OutputStream outputStream = connection.getOutputStream();
// 写入文件数据
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 关闭文件流和连接
fileInputStream.close();
outputStream.flush();
outputStream.close();
// 获取服务器返回的响应
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 上传成功
System.out.println("文件上传成功!");
} else {
// 上传失败
System.out.println("文件上传失败!错误代码:" + responseCode);
}
}
2、通过ServletFileUpload方式上传
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 检查请求是否为multipart/form-data类型
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (isMultipart) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
// 解析请求,获取所有表单项
List<FileItem> items = upload.parseRequest(request);
// 遍历表单项
for (FileItem item : items) {
if (!item.isFormField()) { // 如果是文件字段
String fileName = item.getName();
InputStream fileContent = item.getInputStream();
// 进行文件保存操作,可以根据实际需求自行实现
saveFile(fileName, fileContent);
}
}
response.getWriter().write("文件上传成功!");
} catch (Exception e) {
response.getWriter().write("文件上传失败!");
e.printStackTrace();
}
} else {
response.getWriter().write("请求未包含文件!");
}
}
3、通过MultipartFile方式上传
public String singleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) {
try {
byte[] bytes = file.getBytes();
Path dir = Paths.get(UPLOADED_FOLDER);
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
Files.write(path, bytes);
redirectAttributes.addFlashAttribute("message","上传文件成功:" + path + "");
} catch (Exception e) {
return e.toString();
}
return "redirect:upload_status";
}
该代码没有对上传文件的后缀类型等进行限制,现在我们可以上传一个恶意的jsp脚本到服务器解析执行:
上传成功:
一个非常有效的防御方法是对上传文件后缀名做白名单处理:
public String singleFileUploadSafe(@RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
try {
byte[] bytes = file.getBytes();
String fileName = file.getOriginalFilename();
Path dir = Paths.get(UPLOADED_FOLDER);
Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename());
// 获取文件后缀名
String Suffix = fileName.substring(fileName.lastIndexOf("."));
String[] SuffixSafe = {".jpg", ".png", ".jpeg", ".gif", ".bmp", ".ico"};
boolean flag = false;
for (String s : SuffixSafe) {
if (Suffix.toLowerCase().equals(s)) {
flag = true;
break;
}
}
if (!flag) {
return "只允许上传图片,[.jpg, .png, .jpeg, .gif, .bmp, .ico]";
}
此时继续上传恶意脚本,已经成功拦截:
在审计文件上传漏洞时,关注的重点往往是在上传表单的代码段。我们可以总结出以下一些经典的关键字:
2.文件上传漏洞修复
对于文件上传漏洞的修复一般最有效的方法是限制上传类型并对文件进行重命名,采取白名单策略限制运行上传的类型;对文件名字进行重命名;去除文件名中的特殊字符;上传图片时,通过图片库检测上传文件是否为图片
通过“String Suffix=fileName.substring(fileName.lastIndexOf("."))
”中的“fileName.lastIndexOf(".")
”获取文件的真实后缀,避免了上文中通过“fileName.substring(fileName.indexOf("."))
”获取文件后缀,以.jpg.jsp
的方式绕过检测的情况。通过“String whitetype[]={"image/gif","image/jpeg","image/jpg","image/png"}
”对文件的类型进行判断,虽然可以通过抓包的方式修改Content-Type来绕过,但也有一定限制作用。通过验证后,在文件存储时将文件进行了随机重命名,且重命名后的文件要保证不可预测,不显示在回包和日志文件中,避免上传的文件被恶意利用