Arang.dev
Search

JSP commons-fileupload WAF Bypass

Exploring the JSP commons-fileupload WAF filtering bypass technique through the CCE2019 ENKI challenge

#web-security#waf-bypass#file-upload#jsp
🌐 Translated

This post was machine-translated from Korean by AI. Some expressions may differ from the original.

Read the original (Korean)

The commons-fileupload module is the most commonly used module when implementing file upload functionality in web services built with JSP & Spring Framework.

To trigger and exploit malicious file upload vulnerabilities, one must upload a file with an extension that the web service interprets and executes on the server side (e.g., jsp, jspx, jspl, jsw ...etc...) to achieve arbitrary code execution on the server.

However, many services enforce extension (ext) validation, and this is frequently handled by the WAF (Web Application Firewall).

Additionally, randomizing the filename after validating the extension is a standard countermeasure against file upload vulnerabilities to ensure security.


However, if the extension validation is performed solely at the WAF level rather than the code level, and the randomly generated filename is exposed to the attacker (a situation that seems to occur in quite a few cases), this can be bypassed by leveraging the code implementation characteristics of the commons-fileupload module.

@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);
 }

The code above is the "basic example code" found when searching for commons-fileupload on Google, with the addition of file extension validation code.

From a code perspective, if the WAF validates extensions and manages a whitelist of allowed extensions such as .jpg or .png, it appears that no security vulnerability exists. In fact, until I encountered this technique, I also believed that exploiting the vulnerability in such a situation was impossible.

However, the issue arises from the fact that the commons-fileupload module 지원 RFC2047 starting from version 1.3.

RFC2047 is the specification for MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text. Its main premise is that for non-ASCII text, the requester specifies the encoding method, encodes the text, and transmits it, while the responder decodes and interprets it.

Let's look at the brief code flow of how this is applied in the commons-fileupload module:

  • The List<FileItem> items = fileUpload.parseRequest(request); section in the code above
  • Internally calls private String getFileName(String pContentDisposition) within public List<FileItem> parseRequest(HttpServletRequest req)
  • Calls public Map<String, String> parse within private String getFileName(String pContentDisposition)
  • Calls public static String decodeText(String text) within public Map<String, String> parse
  • Calls private static String decodeWord(String word) within public static String decodeText(String text) and proceeds with decoding the encoded parameters according to the RFC 2047 specification

The process proceeds as shown above.

The encoding format specified in RFC2047 is =?" charset "?" encoding "?" encoded-text "?=.

The description of the encoding methods among these is as follows:

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

The encoding supports Quoted-printable (Q) and Base64 (B). This allows bypassing WAFs that detect strings like .jsp within some_webshell.jsp.

However, the logic where the WAF checks the very last extension has not yet been bypassed.

This can be bypassed by leveraging the characteristics of the RFC2047 implementation in commons-fileupload.

Examining decodeText() λ©”μ„œλ“œ 쀑 인코딩 ν˜•μ‹μ˜ λ¬Έμžμ—΄ νŒŒμ‹± λΆ€λΆ„, encodedTextPos is set using int encodedTextPos = word.indexOf(ENCODED_TOKEN_FINISHER, encodingPos + 1);. Since the subsequent code retrieves only the encoded region, decodes it, and uses it as the file name,

if you insert .jpg at the end of the encoded region using a format like =?UTF8?B?c29tZV93ZWJzaGVsbC5qc3A=?=.jpg, this part is ignored when the actual JSP parses the file name.


If you have reached this point, you can bypass all upload vulnerability checks performed by the WAF, such as 1. Checking for the inclusion of strings like '.jsp' and 2. Checking via lastIndexOf('.').

Subsequently, since the file is created as some_webshell.jsp normally in the JSP code, the web shell upload becomes successful.


Personally, I have noticed that the commons-fileupload module is frequently used when assessing file upload vulnerabilities. I was quite surprised to learn about this technique, as I was previously unaware of it, which led me to hurriedly write and upload this post as soon as I encountered the information.

This post was written with reference to the official write-up by ENKI for the 2019 CCE (Cyber Conflict Exercise).

References :https://enki.co.kr/blog/2020/02/27/cce_writeup.html?fbclid=IwAR0ztC_wshd_DHvIA-HBMh_F99TdqkPiqyGyBu_WfP6Id-2TPTPPp_uPkZY

Comments

Loading comments...
JSP commons-fileupload WAF Bypass | Arang