java基础漏洞学习----文件操作漏洞
java基础漏洞学习----文件操作漏洞
前置基础知识
https://www.cnblogs.com/thebeastofwar/p/17760812.html
文件上传漏洞
文件上传的方式
1.通过文件流
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="uploadFile1" method="post" enctype="multipart/form-data">
<input type="file" name="file" /><br/>
<input type="submit" value="上传" />
</form>
</body>
</html>
UploadFileServlet1.java
package com.example.servletdemo;//根据自己的项目名称更改
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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;
@WebServlet("/uploadFile1")
@MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB
maxFileSize = 1024 * 1024 * 10, // 10MB
maxRequestSize = 1024 * 1024 * 50) // 50MB
public class UploadFileServlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String savePath = "C:/uploads"; // 上传文件保存的目录
File fileSaveDir = new File(savePath);
if (!fileSaveDir.exists()) {
fileSaveDir.mkdirs();
}
for (Part part : request.getParts()) {
String fileName = extractFileName(part);
fileName = new String(fileName.getBytes("ISO-8859-1"), "UTF-8");
InputStream inputStream = part.getInputStream();
OutputStream outputStream = new FileOutputStream(savePath + File.separator + fileName);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
outputStream.close();
inputStream.close();
}
response.getWriter().println("文件上传成功!");
}
private String extractFileName(Part part) {
String contentDisposition = part.getHeader("content-disposition");
String[] items = contentDisposition.split(";");
for (String item : items) {
if (item.trim().startsWith("filename")) {
return item.substring(item.indexOf("=") + 2, item.length() - 1);
}
}
return "";
}
}
2.通过ServletFileUpload
改maven源
在setting里搜索maven
然后在那个目录中新建一个settings.xml
内容,然后点击override
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>huaweimaven</id>
<name>huaweimaven</name>
<url>https://repo.huaweicloud.com/repository/maven/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
然后再pom.xml中alt+insert快捷键添加commons-io和commons-fileupload依赖
主代码
package com.example.servletdemo;
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 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.File;
import java.io.IOException;
import java.util.List;
@WebServlet("/uploadFile2")
public class UploadFileServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 检查是否为文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
response.getWriter().println("请选择文件");
return;
}
// 创建文件上传处理工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
factory.setSizeThreshold(1024 * 1024); // 1MB
// 设置临时文件目录
File tempDir = new File("C:/temp");
factory.setRepository(tempDir);
// 创建文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置请求的最大文件大小
upload.setSizeMax(1024 * 1024 * 10); // 10MB
try {
// 解析文件上传请求
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
// 检查是否为普通表单字段
if (item.isFormField()) {
// 处理普通表单字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
// TODO: 处理普通表单字段的值
} else {
// 处理文件字段
String fieldName = item.getFieldName();
String fileName = item.getName();
// TODO: 处理文件字段的值
File uploadedFile = new File("C:/uploads/" + fileName);
item.write(uploadedFile);
}
}
response.getWriter().println("文件上传成功!");
} catch (FileUploadException e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
}
}
}
3.通过MultipartFile(失败,不知为什么总报404)
在pom.xml中添加Sprintboot MVC相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.3</version>
</dependency>
</dependencies>
主代码
package com.example.servletdemo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
public class UploadFileServlet3 {
@PostMapping("/uploadFile3")
public String uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "请选择文件";
}
String fileName = file.getOriginalFilename();
String filePath = "C:/uploads/";
try {
File dest = new File(filePath + fileName);
file.transferTo(dest);
return "文件上传成功";
} catch (IOException e) {
e.printStackTrace();
return "文件上传失败";
}
}
}
常见java的文件上传漏洞的代码
1.filename.indexof与filename.lastIndexof
shell.png.jsp
如果是filename.indexof获取的就是.png.jsp(即从第一个点获取)
如果是filename.lastIndexof获取的就是.jsp(即从最后一个点获取)
上传webshell测试代码,上传helloworld.png.jsp和shell.png.jsp
index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<form action="uploadFile4" method="post" enctype="multipart/form-data">
<input type="file" name="file" /><br/>
<input type="submit" value="上传" />
</form>
</body>
</html>
主代码
package com.example.servletdemo;
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 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.File;
import java.io.IOException;
import java.util.List;
@WebServlet("/uploadFile4")
public class UploadFileServlet4 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 检查是否为文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
response.getWriter().println("请选择文件");
return;
}
// 创建文件上传处理工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
factory.setSizeThreshold(1024 * 1024); // 1MB
// 设置临时文件目录
File tempDir = new File("C:/temp");
factory.setRepository(tempDir);
// 创建文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置请求的最大文件大小
upload.setSizeMax(1024 * 1024 * 10); // 10MB
try {
// 解析文件上传请求
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
// 检查是否为普通表单字段
if (item.isFormField()) {
// 处理普通表单字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
// TODO: 处理普通表单字段的值
} else {
// 处理文件字段
String fieldName = item.getFieldName();
String fileName = item.getName();
// 获取文件后缀
String fileExtension = fileName.substring(fileName.indexOf(".") + 1);//安全做法 fileName.lastIndexof(".")+1
/*
// 判断后缀是否属于指定的白名单
String[] whiteExtensions = {"png", "jpg", "gif"};
boolean isValidExtension = false;
for (String extension : whiteExtensions) {
if (fileExtension.equalsIgnoreCase(extension)) {
isValidExtension = true;
break;
}
}
if (!isValidExtension) {
response.getWriter().println("文件格式不支持(白名单)");
return;
}
*/
//判断是否属于黑名单
String[] blackExtensions = {"jsp","jspx"};
for (String extension : blackExtensions) {
if (fileExtension.equalsIgnoreCase(extension)) {
response.getWriter().println("文件格式不支持(黑名单)");
return;
}
}
// TODO: 处理文件字段的值
File uploadedFile = new File(getServletContext().getRealPath("/") + "uploads/" + fileName);
item.write(uploadedFile);
//重定向到上传文件的位置 这样上传jsp文件就能解析了
String contextPath = request.getContextPath();
String jspPath = contextPath + "/uploads/" + fileName;
response.sendRedirect(jspPath);
}
}
response.getWriter().println("文件上传成功!");
} catch (FileUploadException e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
}
}
}
然后上传哥斯拉生成的jsp木马,哥斯拉连接,初始目录不是上传webshell的目录,而是tomcat的bin目录
2.前端校验
index2.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<script>
function checkFileType() {
var fileInput = document.getElementById("file");
var file = fileInput.files[0];
var fileType = file.type;
var allowedTypes = ["image/png", "image/jpeg", "image/gif"];
if (!allowedTypes.includes(fileType)) {
alert("请选择图片文件(png, jpg, gif)");
return false;
}
return true;
}
</script>
</head>
<body>
<form action="uploadFile4" method="post" enctype="multipart/form-data" onsubmit="return checkFileType()">
<input type="file" name="file" id="file" /><br/>
<input type="submit" value="上传" />
</form>
</body>
</html>
上传图片马然后修改文件后缀就可以了
3.MIME检测
package com.example.servletdemo;
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 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.File;
import java.io.IOException;
import java.util.List;
@WebServlet("/uploadFile5")
public class UploadFileServlet5 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
// 检查是否为文件上传请求
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if (!isMultipart) {
response.getWriter().println("请选择文件");
return;
}
// 创建文件上传处理工厂
DiskFileItemFactory factory = new DiskFileItemFactory();
// 设置内存缓冲区大小,超过大小的文件将被保存到临时文件目录
factory.setSizeThreshold(1024 * 1024); // 1MB
// 设置临时文件目录
File tempDir = new File("C:/temp");
factory.setRepository(tempDir);
// 创建文件上传处理器
ServletFileUpload upload = new ServletFileUpload(factory);
// 设置请求的最大文件大小
upload.setSizeMax(1024 * 1024 * 10); // 10MB
try {
// 解析文件上传请求
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
// 检查是否为普通表单字段
if (item.isFormField()) {
// 处理普通表单字段
String fieldName = item.getFieldName();
String fieldValue = item.getString("UTF-8");
// TODO: 处理普通表单字段的值
} else {
// 处理文件字段
String fieldName = item.getFieldName();
String fileName = item.getName();
// 获取文件后缀
String fileExtension = fileName.substring(fileName.indexOf(".") + 1);//安全做法 fileName.lastIndexof(".")+1
//判断文件类型
String contentType = item.getContentType();
if (contentType.equals("image/png") || contentType.equals("image/jpeg") || contentType.equals("image/gif") || contentType.equals("application/pdf")) {
// TODO: 处理文件字段的值
File uploadedFile = new File(getServletContext().getRealPath("/") + "uploads/" + fileName);
item.write(uploadedFile);
//重定向到上传文件的位置 这样上传jsp文件就能解析了
String contextPath = request.getContextPath();
String jspPath = contextPath + "/uploads/" + fileName;
response.sendRedirect(jspPath);
response.getWriter().println("文件上传成功!");
}
else
{
response.getWriter().println("文件格式不支持(MIME)");
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
} catch (Exception e) {
e.printStackTrace();
response.getWriter().println("文件上传失败!");
}
}
}
Content-Type修改为image/png等白名单就可以了
4.头部检测
5.内容检测(JSP免杀)
检测常见恶意代码Runtime.getRuntime().exec
等
代码审计技巧
1.搜索org.apache.commons.fileupload java.ioFile
2.搜索.indexOf(
3.搜索MultipartFile,RequestMethod,MultipartHttpServletRequest,CommonsMutipartResolver等
目录遍历漏洞(任意文件读取&下载&删除)
package com.example.servletdemo;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class FileContentServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String filename = request.getParameter("filename"); // 获取GET参数中的文件名
String filePath = System.getProperty("user.dir") + "/" + filename; // 拼接当前工作目录和文件名
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
try {
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
out.println(line);
}
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
out.println("Error: " + e.getMessage());
}
}
}
然后再web.xml中添加
<servlet>
<servlet-name>FileContentServlet</servlet-name>
<servlet-class>com.example.servletdemo.FileContentServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FileContentServlet</servlet-name>
<url-pattern>/file</url-pattern>
</servlet-mapping>
查看源码
zip 自解压
zip解压后的文件为恶意文件
package com.example.servletdemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
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;
@WebServlet("/uploadFile6")
@MultipartConfig
public class UploadFileServlet6 extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
String uploadPath = getServletContext().getRealPath("/") + "uploads"; // 获取项目根目录并指定上传文件保存的目录
// 创建上传文件保存的目录(如果不存在)
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 获取上传的文件
Part filePart = request.getPart("file");
String fileName = getFileName(filePart);
// 保存上传的文件到指定目录
String filePath = uploadPath + File.separator + fileName;
try (InputStream inputStream = filePart.getInputStream();
FileOutputStream outputStream = new FileOutputStream(filePath)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
// 解压上传的zip文件
String unzipPath = getServletContext().getRealPath("/") + "unzips"; // 获取项目根目录并指定解压目录
// 创建解压目录(如果不存在)
File unzipDir = new File(unzipPath);
if (!unzipDir.exists()) {
unzipDir.mkdirs();
}
unzip(filePath, unzipPath);
// 检查解压后的文件数量 如果数量为1 则跳转到解压文件位置 这样上传shell就可以解析了
File[] unzippedFiles = unzipDir.listFiles();
if (unzippedFiles != null && unzippedFiles.length == 1 && unzippedFiles[0].isFile()) {
String contextPath = request.getContextPath();
String jspPath = contextPath + "/unzips/" + unzippedFiles[0].getName();
response.sendRedirect(jspPath);
} else {
response.getWriter().println("文件上传和解压成功!");
}
}
// 获取上传文件的名称
private String getFileName(Part part) {
String contentDisposition = part.getHeader("content-disposition");
String[] elements = contentDisposition.split(";");
for (String element : elements) {
if (element.trim().startsWith("filename")) {
return element.substring(element.indexOf('=') + 1).trim().replace("\"", "");
}
}
return null;
}
// 解压zip文件
private void unzip(String zipFilePath, String destDirectory) throws IOException {
try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath))) {
byte[] buffer = new byte[4096];
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String entryFileName = zipEntry.getName();
String entryPath = destDirectory + File.separator + entryFileName;
if (zipEntry.isDirectory()) {
File dir = new File(entryPath);
dir.mkdirs();
} else {
try (FileOutputStream outputStream = new FileOutputStream(entryPath)) {
int bytesRead;
while ((bytesRead = zipInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
}
zipEntry = zipInputStream.getNextEntry();
}
}
}
}