JSP commons-fileupload WAF Bypass
通过 CCE2019 ENKI 题目了解的 JSP commons-fileupload WAF 过滤绕过技术
本文由 AI 从韩语机器翻译,部分表达可能与原文有所不同。
阅读原文(韩语)在用 JSP & Spring Framework 实现的 Web 服务中实现文件上传功能时,最常用的模块就是commons-fileupload模块。
要触发恶意的文件上传漏洞进行攻击,就必须能够以服务器端 Web 服务可解析并执行的扩展名(例如 jsp、jspx、jspl、jsw ...etc..)进行上传,从而在服务器内部执行任意代码。
但是,很多服务都会进行扩展名(ext) 检查,而且这项检查往往是在 WAF(Web Application Firewall) 上进行的。
此外,在进行扩展名检查之后,对文件名进行 randomizing(随机化)以增强安全性,是常见的文件上传漏洞应对方法。
但是,如果扩展名检查不是在代码层面、而仅在 WAF 层面执行,并且 random 生成的文件名泄露给了攻击者(这种情况似乎在相当多的案例中都能发现),那么就可以利用commons-fileupload模块的代码实现特征来绕过它。
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
request.setCharacterEncoding(CHARSET);
PrintWriter out = response.getWriter();
File attachesDir = new File(ATTACHES_DIR);
DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
fileItemFactory.setRepository(attachesDir);
fileItemFactory.setSizeThreshold(LIMIT_SIZE_BYTES);
ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);
try {
List<FileItem> items = fileUpload.parseRequest(request);
for (FileItem item : items) {
if (item.isFormField()) {
System.out.printf("파라미터 명 : %s, 파라미터 값 : %s \n", item.getFieldName(), item.getString(CHARSET));
} else {
System.out.printf("파라미터 명 : %s, 파일 명 : %s, 파일 크기 : %s bytes \n", item.getFieldName(),
item.getName(), item.getSize());
if (item.getSize() > 0) {
Object fileName = new File(item.getName()).getName();
fileName = this.makeFn((String)fileName) + "." + this.getExt((String)fileName); // makeFn -> make random filename
String filePath = uploadPath + File.separator + (String)fileName;
File storeFile = new File(filePath);
item.write(storeFile);
}
}
}
out.println("<h1>파일 업로드 완료</h1>");
} catch (Exception e) {
// 파일 업로드 처리 중 오류가 발생하는 경우
e.printStackTrace();
out.println("<h1>파일 업로드 중 오류가 발생하였습니다.</h1>");
}
}
//출처: https://dololak.tistory.com/720 [코끼리를 냉장고에 넣는 방법]private String getExt(String fileName) {
if (fileName.lastIndexOf(".") == -1)
return "noext";
fileName = fileName.substring(fileName.lastIndexOf(".") + 1).trim();
if ("".equals(fileName))
return "noext";
return fileName.substring(fileName.lastIndexOf(".") + 1);
}上面的代码是在谷歌中搜索commons-fileupload时出现的'基本示例代码'的基础上,添加了文件扩展名检查代码后的代码。
从实际代码来看,如果 WAF 进行扩展名检查,仅将 .jpg、.png 等允许的扩展名作为 whitelist 来管理,那么看起来似乎不会产生安全漏洞。而且实际上,在接触这个技术之前,我也一直认为在这种情况下漏洞利用是不可能的。
但问题就始于:commons-fileupload模块从版本 1.3 开始支持RFC2047这一点。
RFC2047是关于MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text的规范,其主要内容是:对于 Non ascii text,由请求方指定编码方式进行编码后发送,响应方再对其进行解码和解析。
把它在commons-fileupload模块中的应用用简要的代码流程来看一下,
- 上述代码中的
List<FileItem> items = fileUpload.parseRequest(request);部分 - 在
public List<FileItem> parseRequest(HttpServletRequest req)中,内部调用private String getFileName(String pContentDisposition) - 在
private String getFileName(String pContentDisposition)中调用public Map<String, String> parse - 在
public Map<String, String> parse中调用public static String decodeText(String text) - 在
public static String decodeText(String text)中调用private static String decodeWord(String word),并进行RFC 2047规范中已编码参数的解码
过程如上所示。
RFC2047 中规定的编码格式为=?" charset "?" encoding "?" encoded-text "?=。
其中,关于 encoding 方式的说明如下
4. Encodings
Initially, the legal values for "encoding" are "Q" and "B". These
encodings are described below. The "Q" encoding is recommended for
use when most of the characters to be encoded are in the ASCII
character set; otherwise, the "B" encoding should be used.
...중략...
4.1. The "B" encoding
The "B" encoding is identical to the "BASE64" encoding defined by RFC编码支持明文(Q) 和 Base64(B)。通过这一点,可以绕过那些在some_webshell.jsp中捕捉.jsp这类词语的 WAF。
但是,仍然无法绕过 WAF 中检查最后一个扩展名的逻辑。
这一点可以利用 commons-fileupload 实现 RFC2047 的特征来绕过。
查看decodeText() 方法中解析编码格式字符串的部分可以发现,它用int encodedTextPos = word.indexOf(ENCODED_TOKEN_FINISHER, encodingPos + 1);设置 encodedTextPos,并且在之后的代码中只取出编码区域进行解码,然后将其作为文件名,因此
如果像=?UTF8?B?c29tZV93ZWJzaGVsbC5qc3A=?=.jpg这种格式那样,在编码区域结束的位置后面插入 .jpg,那么实际在 jsp 中解析文件名时,这部分会被忽略。
到这一步为止,就可以绕过 WAF 执行的1. 是否包含 '.jsp' 这类字符串的检查、2. lastIndexOf('.')等上传漏洞检查。
之后在 jsp 代码中,文件会正常地以some_webshell.jsp被创建,因此就能够成功上传 Web shell。
就我个人而言,在进行文件上传漏洞诊断时也感觉到 commons-fileupload 模块被大量使用,而我之前并不知道竟然存在这样的技术,看到相关内容时也非常惊讶,所以一接触到这些内容就赶紧写了这篇文章并上传。
本文是参考ENKI在2019年 CCE(网络攻防大赛)中出题的官方 Writeup撰写的。
评论