浅析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——
-
请求方法为GET、获取参数className(空)\methodName(空)\encode(非空)
-
由于className与methodName为空,通过
RMICoder
解码内容为其赋值 -
判断类与方法名在白名单中,Filter校验通过
—–Servlet—-
-
通过
request.getParameter
未获取到类、方法以及参数 -
判断Header的Content-Type头存在
multipart/form-data;
-
解析Body,获取到真实执行的类、方法以及参数,最终完成调用
因此V9系统下的分析就完成了,补个利用截图(V9)
当然其实在V11当中这个解析也有一定差异
在V11系统当中不能通过encode+multipart的组合姿势完成绕过
就当是留个小作业吧,V11当中到底有什么差异呢?又该如何构造绕过的Payload呢?利用截图(V11)
- source:y4tacker
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论