Web应用通常都会包含文件上传功能,用户可以将其本地的文件上传到Web服务器上。如果服务器端没有能够正确的检测用户上传的文件类型是否合法(例如上传了jsp后缀的WebShell)就将文件写入到服务器中就可能会导致服务器被非法入侵。
漏洞成因
后缀名无限制
// 导入必要的类库
package com.example.upload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.UUID;
// 定义Servlet,并指定映射路径为"/upload"
@WebServlet("/upload")
public class UploadServlet extends HttpServlet {
// 覆盖父类的service方法
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置请求和响应的字符编码
req.setCharacterEncoding("UTF-8");
resp.setContentType("text/html;charset=utf-8");
// 创建DiskFileItemFactory实例
DiskFileItemFactory factory = new DiskFileItemFactory();
// 获取临时目录,并确保目录存在
File tempDir = new File(req.getSession().getServletContext().getRealPath("tmp"));
if (!tempDir.exists()) {
tempDir.mkdirs();
}
factory.setRepository(tempDir);
// 创建ServletFileUpload实例,并设置字符编码
ServletFileUpload fileUpload = new ServletFileUpload(factory);
fileUpload.setHeaderEncoding("UTF-8");
try {
// 解析请求,获取FileItem列表
List<FileItem> fileItems = fileUpload.parseRequest(req);
// 遍历处理每个FileItem
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
// 如果是普通表单字段,获取字段名和值
String fieldName = fileItem.getFieldName();
String fieldValue = fileItem.getString("utf-8");
// 这里可以对普通表单字段进行处理
} else {
// 如果是文件字段,处理文件上传
// 获取原始文件名、文件扩展名、生成唯一文件名
String originalFileName = fileItem.getName();
String fileExtension = FilenameUtils.getExtension(originalFileName);
String fileUUIDName = UUID.randomUUID().toString();
String uploadedFileName = fileUUIDName + "." + fileExtension;
// 构建上传路径
String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + uploadedFileName);
// 创建上传文件的File对象及其父目录
File uploadedFile = new File(uploadPath);
uploadedFile.getParentFile().mkdirs();
uploadedFile.createNewFile();
// 使用输入流和输出流实现文件拷贝
try (FileOutputStream out = new FileOutputStream(uploadedFile);
InputStream in = fileItem.getInputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
// 删除临时文件
fileItem.delete();
// 输出上传路径
PrintWriter writer = resp.getWriter();
writer.write("上传路径:" + uploadPath);
}
}
} catch (FileUploadException e) {
// 处理文件上传异常
throw new RuntimeException(e);
}
}
}
// 获取临时目录,并确保目录存在
File tempDir = new File(req.getSession().getServletContext().getRealPath("tmp"));
req.getSession(): 获取当前请求的HttpSession对象,HttpSession是用于跟踪用户会话信息的对象。
getServletContext(): 获取HttpSession所关联的ServletContext对象,ServletContext代表了整个Web应用程序的上下文。
getRealPath("tmp"): 获取指定虚拟路径("tmp")在文件系统中的真实路径。这里指的是应用程序的临时目录。
new File(...): 用获取到的真实路径创建一个File对象,表示应用程序的临时目录。
// 创建DiskFileItemFactory实例
DiskFileItemFactory factory = new DiskFileItemFactory();
DiskFileItemFactory是Apache Commons FileUpload库中的类,用于创建FileItem对象的工厂。在文件上传过程中,FileItem对象代表了一个表单字段或文件。DiskFileItemFactory特别用于将文件项(FileItem)存储到硬盘上而不是内存中,以避免内存溢出的问题。
具体作用包括:
创建FileItem对象: 通过DiskFileItemFactory创建FileItem实例,以便在后续的文件上传过程中存储表单字段或文件的信息。
配置工厂属性: 设置工厂的属性,例如临时存储目录(Repository),这是一个用于存储上传文件的临时目录,以及其他一些配置,以确保文件上传的正常进行。
漏洞分析
// 获取文件扩展名
String fileExtension = FilenameUtils.getExtension(originalFileName);
//构建上传路径
String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + uploadedFileName);
黑白名单
黑名单
package com.example;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@WebServlet("/upload1")
public class UploadServlet1 extends HttpServlet {
private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码集(解码)
req.setCharacterEncoding("UTF-8");
// 设置响应的编码集
resp.setContentType("text/html;charset=utf-8");
DiskFileItemFactory factory = new DiskFileItemFactory();
File f = new File(req.getSession().getServletContext().getRealPath("tmp"));
if (!f.exists()) {
f.mkdirs();
}
factory.setRepository(f);
ServletFileUpload fileupload = new ServletFileUpload(factory);
fileupload.setHeaderEncoding("UTF-8");
try {
List<FileItem> fileItems = fileupload.parseRequest(req);
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
String fieldName = fileItem.getFieldName();
String fieldvalue = fileItem.getString("utf-8");
} else {
String name = fileItem.getName();
String extName = "." + FilenameUtils.getExtension(name);
for (String s : this.donExtList) {
if (s.equals(extName)) {
resp.getWriter().write("禁止上传改类型文件");
return;
}
}
String fileUUIDName = UUID.randomUUID().toString();
String upFileName = fileUUIDName + extName;
String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + upFileName);
//创建文件
File file = new File(uploadPath);
file.getParentFile().mkdirs();
file.createNewFile();
//创建文件输出流
FileOutputStream out = new FileOutputStream(file);
//获取文件输入流
InputStream in = fileItem.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.close();
//删除fileTtem对象
fileItem.delete();
PrintWriter writer = resp.getWriter();
writer.write("上传路径:" + uploadPath);
}
}
} catch (FileUploadException e) {
throw new RuntimeException(e);
}
}
}
漏洞分析
private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");
//黑名单不全
白名单
package com.example;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@WebServlet("/upload4")
public class UploadServlet4 extends HttpServlet {
private List<String> donExtList= Arrays.asList(".jsp",".php",".java",".class");
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码集(解码)
req.setCharacterEncoding("UTF-8");
// 设置响应的编码集
resp.setContentType("text/html;charset=utf-8");
DiskFileItemFactory factory = new DiskFileItemFactory();
File f = new File(req.getSession().getServletContext().getRealPath("tmp"));
if (!f.exists()) {
f.mkdirs();
}
factory.setRepository(f);
ServletFileUpload fileupload = new ServletFileUpload(factory);
fileupload.setHeaderEncoding("UTF-8");
try {
List<FileItem> fileItems = fileupload.parseRequest(req);
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
String fieldName = fileItem.getFieldName();
String fieldvalue = fileItem.getString("utf-8");
} else {
String name = fileItem.getName();
String extName = "." + FilenameUtils.getExtension(name);
for (String s : this.donExtList) {
if (s.equals(extName)) {
String fileUUIDName = UUID.randomUUID().toString();
String upFileName = fileUUIDName + extName;
String uploadPath = req.getSession().getServletContext().getRealPath("uploads/" + upFileName);
//创建文件
File file = new File(uploadPath);
file.getParentFile().mkdirs();
file.createNewFile();
//创建文件输出流
FileOutputStream out = new FileOutputStream(file);
//获取文件输入流
InputStream in = fileItem.getInputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
in.close();
out.close();
//删除fileTtem对象
fileItem.delete();
PrintWriter writer = resp.getWriter();
writer.write("上传路径:" + uploadPath);
break;
}
}
}
}
} catch (FileUploadException e) {
throw new RuntimeException(e);
}
}
}
上传参数可控
package com.example;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@MultipartConfig
@WebServlet("/upload2")
public class UploadServlet2 extends HttpServlet {
private List<String> donExtList= Arrays.asList(".jspx",".jsp",".php",".java",".class");
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 设置编码集(解码)
req.setCharacterEncoding("UTF-8");
// 设置响应的编码集
resp.setContentType("text/html;charset=utf-8");
Part part = req.getPart("file");
String fileUUIDName = UUID.randomUUID().toString();
String realName = part.getSubmittedFileName();;
String ext = realName.substring(realName.lastIndexOf("."));
for (String s : this.donExtList) {
if (s.equals(ext)) {
resp.getWriter().write("禁止上传改类型文件");
return;
}
}
String path = null == req.getParameter("path")?"":req.getParameter("path");
String ServletPath = req.getSession().getServletContext().getRealPath("uploads/");
String uploadPath =ServletPath+ path+fileUUIDName+ext;
resp.getWriter().write("上传成功:"+uploadPath);
FileOutputStream stream = new FileOutputStream(uploadPath);
IOUtils.copy(part.getInputStream(),stream);
}
}
漏洞分析
String path = null == req.getParameter("path")?"":req.getParameter("path");
//参数可控
jar -cvf test.war cmd.jsp
其他漏洞
除了以上三种常见漏洞 还有其他漏洞 例如 文件头检测 制作图片一句话就可以绕过。
文件类型检测 上传时候修改Content-Type: image/jpeg
类型就可以绕过。
5.防御建议
1.设置上传白名单 例如只允许上传 png gif jpg等图片类型后缀
2.不要使用客户端可控参数 需要使用时必须过滤。
3.上传目录 禁止解析脚本文件。