浅析SmartBi逻辑漏洞-3

admin 2024年10月29日00:31:28评论13 views字数 7023阅读23分24秒阅读模式

浅析SmartBi逻辑漏洞(3)

前言

这个系列终于到了第三篇,指条路,如果忘记了可以再看看之前写的文章

浅析Smartbi逻辑漏洞

浅析Smartbi逻辑漏洞(2)

之前我就曾在第二篇末尾提到过(没人继续深入看),仍然存在一个问题,今天这个问题终于得以修复

浅析SmartBi逻辑漏洞-3

当然老规矩,这里仅分享逻辑漏洞部分补丁绕过思路,不提供完整payload

补丁

补丁中新增了一个规则

12345
"rules": [{    "className": "*",    "methodName": "*",    "type": "RejectGetAndFormData"}

一眼丁真,鉴定为不能同时使用GET与Multipart

12345678910111213141516171819202122232425262728
protected int patchRMI(String className, String methodName, HttpServletRequest request, HttpServletResponse response, FilterChain chain) {    String contentType = request.getContentType();    if (this.isNullOrEmpty(contentType)) {        contentType = "";    }    String method = request.getMethod();    if (this.isNullOrEmpty(method)) {        method = "";    }    if ("get".equals(method.toLowerCase(Locale.ENGLISH)) && contentType.toLowerCase(Locale.ENGLISH).startsWith("multipart/form-data")) {        return 1;    } else {        String params = request.getParameter("params");        if (params == null) {            params = (String)request.getAttribute("params");        }        if (this.isNotNullAndEmpty(className) && this.isNotNullAndEmpty(methodName) && this.isNotNullAndEmpty(params)) {            request.setAttribute("className", className);            request.setAttribute("methodName", methodName);            request.setAttribute("params", params);        }        return 0;    }}

利用分析

其实这个有两种打法,因为两个版本代码不一样,这里我们以V9代码为例

简单做个回顾(详细的自己看老版本分析),当我们调用RMIServlet之前需要绕过CheckIsLoggedFilter的判断,保证在CheckIsLoggedFilter中指向的类与方法在白名单当中,而在RMIServlet实际调用时指向真正要执行黑名单方法

接下来回到CheckIsLoggedFilter,如果我们请求是GET方法,之后就会通过httpRequest.getParameter获取我们的className\methodName\encode参数

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
boolean isRmi = "/vision/RMIServlet".equals(((HttpServletRequest)httpRequest).getServletPath());    String requestStr;    if (isRmi) {        String queryString = ((HttpServletRequest)httpRequest).getQueryString();        String params;        if (queryString != null && queryString.startsWith("windowUnloading")) {            params = queryString.length() > "windowUnloading".length() && queryString.charAt("windowUnloading".length()) == '=' ? "windowUnloading=&" : "windowUnloading&";           .......此处省略.......        } else if ("GET".equals(((HttpServletRequest)httpRequest).getMethod())) {            className = ((HttpServletRequest)httpRequest).getParameter("className");            methodName = ((HttpServletRequest)httpRequest).getParameter("methodName");            encode = ((HttpServletRequest)httpRequest).getParameter("encode");        }        if (encode != null && className == null && methodName == null) {            String[] decode = RMICoder.decode(encode);            className = decode[0];            methodName = decode[1];            params = decode[2];            ((HttpServletRequest)httpRequest).setAttribute("className", className);            ((HttpServletRequest)httpRequest).setAttribute("methodName", methodName);            ((HttpServletRequest)httpRequest).setAttribute("params", params);            ((HttpServletRequest)httpRequest).setAttribute("request_encoded", Boolean.TRUE);        }        if (className == null && methodName == null && (((HttpServletRequest)httpRequest).getContentType() == null || !((HttpServletRequest)httpRequest).getContentType().startsWith("multipart/form-data;"))) {            ByteArrayOutputStream baos = new ByteArrayOutputStream();            byte[] buff = new byte[4096];            InputStream is = ((HttpServletRequest)httpRequest).getInputStream();            int readed;            while((readed = is.read(buff)) >= 0) {                baos.write(buff, 0, readed);            }            requestStr = baos.toString("UTF-8");            String[] requestParams = requestStr.split("\\&");            String encodeValue = null;            String[] decode = requestParams;            int var19 = requestParams.length;            for(int var20 = 0; var20 < var19; ++var20) {                String param = decode[var20];                int index = param.indexOf(61);                if (index != -1) {                    String key = param.substring(0, index);                    String value = param.substring(index + 1);                    value = URLDecoder.decode(value, "UTF-8");                    if (encodeValue == null && "encode".equals(key)) {                        encodeValue = value;                    }                    ((HttpServletRequest)httpRequest).setAttribute(key, value);                }            }            className = (String)((HttpServletRequest)httpRequest).getAttribute("className");            methodName = (String)((HttpServletRequest)httpRequest).getAttribute("methodName");            if (className == null && methodName == null && encodeValue != null) {                decode = RMICoder.decode(encodeValue);                className = decode[0];                methodName = decode[1];                String params = decode[2];                ((HttpServletRequest)httpRequest).setAttribute("className", className);                ((HttpServletRequest)httpRequest).setAttribute("methodName", methodName);                ((HttpServletRequest)httpRequest).setAttribute("params", params);                ((HttpServletRequest)httpRequest).setAttribute("request_encoded", Boolean.TRUE);            }            if (LOG.isTraceEnabled()) {                LOG.trace("Get parameter 'className' return null. Parse request.getInputStream() result:" + className + "." + methodName);            }        }    }

接下来我们再来回顾RMIServlet中对参数的获取(smartbi.util.RMIUtil#parseRMIInfo(javax.servlet.http.HttpServletRequest, boolean))

首先尝试通过request.getParameter尝试获取className\methodName\params参数,如果为空则通过自定义实现的Multipart做解析获取

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
public static RMIInfo parseRMIInfo(HttpServletRequest request, boolean forceParse) {    if (!"/vision/RMIServlet".equals(request.getServletPath()) && !forceParse) {        return null;    } else {        RMIInfo info = getRMIInfoFromRequest(request);        if (info != null) {            return info;        } else {            String className = request.getParameter("className");            String methodName = request.getParameter("methodName");            String params = request.getParameter("params");            if (StringUtil.isNullOrEmpty(className) && StringUtil.isNullOrEmpty(methodName) && StringUtil.isNullOrEmpty(params) && request.getContentType() != null && request.getContentType().startsWith("multipart/form-data;")) {                DiskFileItemFactory dfif = new DiskFileItemFactory();                ServletFileUpload upload = new ServletFileUpload(dfif);                String encodeString = null;                try {                    List<FileItem> fileItems = upload.parseRequest(request);                    request.setAttribute("UPLOAD_FILE_ITEMS", fileItems);                    Iterator var10 = fileItems.iterator();                    while(var10.hasNext()) {                        FileItem fileItem = (FileItem)var10.next();                        if (fileItem.isFormField()) {                            String itemName = fileItem.getFieldName();                            String itemValue = fileItem.getString("UTF-8");                            if ("className".equals(itemName)) {                                className = itemValue;                            } else if ("methodName".equals(itemName)) {                                methodName = itemValue;                            } else if ("params".equals(itemName)) {                                params = itemValue;                            } else if ("encode".equals(itemName)) {                                encodeString = itemValue;                            }                        }                    }                } catch (UnsupportedEncodingException | FileUploadException var14) {                    LOG.error(var14.getMessage(), var14);                }                if (!StringUtil.isNullOrEmpty(encodeString)) {                    String[] decode = (String[])((String[])CodeEntry.decode(encodeString, true));                    className = decode[0];                    methodName = decode[1];                    params = decode[2];                }            }            if (className == null && methodName == null) {                className = (String)request.getAttribute("className");                methodName = (String)request.getAttribute("methodName");                params = (String)request.getAttribute("params");            }            info = new RMIInfo();            info.setClassName(className);            info.setMethodName(methodName);            info.setParams(params);            request.setAttribute("ATTR_KEY_RMIINFO", info);            return info;        }    }}

看完以后,聪明的同学已经能想到一个解析差异的问题

如果我们将请求方法设置为GET,在queryString中仅传入encode参数(白名单类方法),再将真实要执行的放在Multipart部分不就能绕过Filter的校验了么

将以上带入执行流程做个简单梳理:

—–Filter——

  1. 请求方法为GET、获取参数className(空)\methodName(空)\encode(非空)

  2. 由于className与methodName为空,通过RMICoder解码内容为其赋值

  3. 判断类与方法名在白名单中,Filter校验通过

    —–Servlet—-

  4. 通过request.getParameter未获取到类、方法以及参数

  5. 判断Header的Content-Type头存在multipart/form-data;

  6. 解析Body,获取到真实执行的类、方法以及参数,最终完成调用

因此V9系统下的分析就完成了,补个利用截图(V9)

浅析SmartBi逻辑漏洞-3

当然其实在V11当中这个解析也有一定差异

在V11系统当中不能通过encode+multipart的组合姿势完成绕过

就当是留个小作业吧,V11当中到底有什么差异呢?又该如何构造绕过的Payload呢?利用截图(V11)

浅析SmartBi逻辑漏洞-3

- source:y4tacker

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:31:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅析SmartBi逻辑漏洞-3https://cn-sec.com/archives/3314493.html

发表评论

匿名网友 填写信息