51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

浅析SmartBi逻辑漏洞(3)

浅析SmartBi逻辑漏洞(3) {#浅析SmartBi逻辑漏洞-3}

前言 {#前言}

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

浅析Smartbi逻辑漏洞

浅析Smartbi逻辑漏洞(2)

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

image-20240419101828056

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

补丁 {#补丁}

补丁中新增了一个规则

|-------------------|-------------------------------------------------------------------------------------------| | 1 2 3 4 5 | "rules": [{ "className": "*", "methodName": "*", "type": "RejectGetAndFormData" } |

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

|------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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参数

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | 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做解析获取

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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)

8f6210bdf50374a6b2e3221f1fa19e5

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

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

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

8349e0734eb66c79a033ae0b796858a

赞(0)
未经允许不得转载:工具盒子 » 浅析SmartBi逻辑漏洞(3)