最近 Confluence 官方通报了一个严重漏洞 CVE-2022-26134 :
从漏洞描述来看,这仍然是一个 OGNL 表达式注入漏洞。影响版本如下:
from 1.3.0 before 7.4.17
from 7.13.0 before 7.13.7
from 7.14.0 before 7.14.3
from 7.15.0 before 7.15.2
from 7.16.0 before 7.16.4
from 7.17.0 before 7.17.4,
from 7.18.0 before 7.18.1
补丁描述:
主要是修改了 `xwork-1.0.3-atlassian-10.jar` 。下面将深入分析漏洞原理,并尝试绕过沙箱构造命令执行结果回显。
新版本主要是修改了 `xwork-1.0.3-atlassian-10.jar` 。首先简单进行一下补丁对比:
改动的地方很多,但是最关键的地方位于 `ActionChainResult#execute` 函数,对提取 `finalNamespace` 和 `finalActionName` 的过程进行了更新。
Confluence 基于 Struts 架构进行开发。我们首先以登录请求为例,对 Confluence 请求处理的流程进行动态调试。访问 `/login.action` ,经过一系列 `Filter` 处理后,将进入Servlet 的分发器 `ServletDispatcher` (本质上是其子类 `ConfluenceServletDispatcher` 对象):
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {
try {
if (this.paramsWorkaroundEnabled) {
request.getParameter("foo");
}
request = this.wrapRequest(request);
this.serviceAction(request, response, this.getNameSpace(request), this.getActionName(request), this.getRequestMap(request), this.getParameterMap(request), this.getSessionMap(request), this.getApplicationMap());
} catch (IOException var5) {
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
log.error(message, var5);
this.sendError(request, response, 500, new ServletException(message, var5));
}
}
分别通过函数 `getNameSpace` 、 `getActionName` 、 `getRequestMap` 、 `getSessionMap` 、 `getApplicationMap` 提取相应参数,对应关系如下:
`getNameSpace(request)` -> `namespace`
`getActionName(request)` -> `actionName`
`getRequestMap(request)` -> `requestMap`
`getParameterMap(request)` -> `parameterMap`
`getSessionMap(request)` -> `sessionMap`
`getApplicationMap()` -> `applicationMap`
然后调用 `serviceAction` 函数,进入子类 `ConfluenceServletDispatcher` 的 `serviceAction` 函数:
实例化 `DefaultActionProxy` 对象,调用其 `execute` 函数:
进入 `DefaultActionInvocation#invoke` :
这里开始调用 Struts Interceptor 拦截器对象对请求进行处理, `DefaultActionInvocation` 对象拦截器集合 `interceptors` 一共有 32 个:
函数 `invoke` 尝试通过 `next` 获取下一个拦截器对象,然后调用其 `intercept` 方法,大部分 `Interceptor` 对象的 `intercept` 函数格式如下所示:
继续调用 `DefaultActionInvocation#invoke` ,从而形成迭代循环。但是在调试中我们发现也有特殊的一些 `Interceptor` ,比如 `ConfluenceAccessInterceptor` :
当满足一定条件时并不会继续调用 `DefaultActionInvocation#invoke` ,而是返回字符串 `notpermitted` ,我们分析一下 `isAccessPermitted` 函数:
主要是通过请求的 `*.action` 和 `methdName` ,来判断当前用户 `currentUser` 是否有访问权限。也就是说,当访问一个无权访问的 `*.action` 时,`DefaultActionInvocation#invoke` 在迭代调用到 `ConfluenceAccessInterceptor#intercept` 后,将返回 `notpermitted` 并赋值给 `resultCode`,从而跳出迭代。我们替换测试请求为 `/index.action`:
当处理到 `ConfluenceAccessInterceptor` 拦截器时,将不会继续迭代调用下一个拦截器,而是继续往下走,进入 `executeResult` 函数:
进入 `ActionChainResult#execute` :
提取 `namespace` 参数,并调用 `translateVariables` 函数,进入:
典型的 OGNL 表达式解析过程,前面分析中可知, `namespace` 参数通过 `ServletDispatcher#getNameSpace` 函数获取,查看定义:
可见 `namespace` 取值为请求 `servletPath` 最后一个 `/` 之前的部分。
根据上面正则表达式规则,要想触发 OGNL 解析,我们很容易构造出相应的 URL :
成功触发 OGNL 表达式注入。
网上现在已经公开的一些利用方式简单粗暴,只能针对 v7.14 及以下系列有效,因为从 v7.15 系列开始,Confluence 在 OGNL 表达式解析时加入了沙箱设置:
进入 `isSafeExpression` 函数:
主要的黑名单如下:
(1) `unsafePropertyNames`
0 = "sun.misc.Unsafe"
1 = "classLoader"
2 = "java.lang.System"
3 = "java.lang.ThreadGroup"
4 = "com.opensymphony.xwork.ActionContext java.lang.Compiler"
5 = "com.atlassian.applinks.api.ApplicationLinkRequestFactory"
6 = "java.lang.Thread"
7 = "com.atlassian.core.util.ClassLoaderUtils"
8 = "java.lang.ProcessBuilder"
9 = "java.lang.InheritableThreadLocal"
10 = "com.atlassian.core.util.ClassHelper"
11 = "class"
12 = "java.lang.Shutdown"
13 = "java.lang.ThreadLocal"
14 = "java.lang.Process"
15 = "java.lang.Package"
16 = "org.apache.tomcat.InstanceManager"
17 = "java.lang.Runtime"
18 = "javax.script.ScriptEngineManager"
19 = "javax.persistence.EntityManager"
20 = "org.springframework.context.ApplicationContext"
21 = "java.lang.SecurityManager"
22 = "java.lang.Object"
23 = "java.lang.Class"
24 = "java.lang.RuntimePermission"
25 = "javax.servlet.ServletContext"
26 = "java.lang.ClassLoader"
(2) `unsafePackageNames`
0 = "java.rmi"
1 = "sun.management"
2 = "org.apache.catalina.session"
3 = "java.jms"
4 = "com.atlassian.confluence.util.io"
5 = "com.google.common.reflect"
6 = "javax.sql"
7 = "java.nio"
8 = "com.atlassian.sal.api.net"
9 = "sun.invoke"
10 = "java.util.zip"
11 = "liquibase"
12 = "com.hazelcast"
13 = "org.apache.commons.httpclient"
14 = "com.atlassian.util.concurrent"
15 = "java.net"
16 = "freemarker.ext.jsp"
17 = "com.sun.jna"
18 = "net.java.ao"
19 = "javax"
20 = "sun.corba"
21 = "org.springframework.util.concurrent"
22 = "com.sun.jmx"
23 = "sun.misc"
24 = "javassist"
25 = "ognl"
26 = "org.apache.commons.exec"
27 = "com.atlassian.cache"
28 = "org.wildfly.extension.undertow.deployment java.lang.reflect"
29 = "io.atlassian.util.concurrent"
30 = "java.util.concurrent"
31 = "com.atlassian.confluence.util.http"
32 = "sun.tracing"
33 = "org.objectweb.asm"
34 = "freemarker.template"
35 = "net.sf.hibernate"
36 = "freemarker.core"
37 = "net.bytebuddy"
38 = "org.apache.tomcat"
39 = "freemarker.ext.rhino"
40 = "com.atlassian.media"
41 = "org.springframework.context"
42 = "org.apache.velocity"
43 = "javax.xml"
44 = "java.sql"
45 = "sun.reflect"
46 = "sun.net"
47 = "javax.persistence"
48 = "org.javassist"
49 = "javax.naming"
50 = "org.apache.httpcomponents.httpclient"
51 = "com.atlassian.hibernate"
52 = "sun.nio"
53 = "com.atlassian.confluence.impl.util.sandbox"
54 = "com.google.common.net"
55 = "com.atlassian.filestore"
56 = "org.apache.commons.io"
57 = "com.atlassian.vcache"
58 = "jdk.nashorn"
59 = "sun.launcher"
60 = "oshi"
61 = "org.apache.bcel"
62 = "sun.rmi"
63 = "sun.tools.jar"
64 = "org.springframework.expression.spel"
65 = "com.opensymphony.xwork.util"
66 = "org.ow2.asm"
67 = "com.atlassian.confluence.setup.bandana"
68 = "org.quartz"
69 = "net.sf.cglib"
70 = "com.atlassian.activeobjects"
71 = "com.atlassian.utils.process"
72 = "sun.security"
73 = "com.atlassian.quartz"
74 = "javax.management"
75 = "sun.awt.shell"
76 = "com.google.common.cache"
77 = "org.apache.http.client"
78 = "java.io"
79 = "com.atlassian.confluence.util.sandbox"
80 = "java.util.jar"
81 = "com.atlassian.scheduler"
82 = "sun.print"
83 = "com.atlassian.failurecache"
84 = "com.google.common.io"
85 = "org.apache.catalina.core"
86 = "org.ehcache"
(3) `unsafeMethodNames`
0 = "getClass"
1 = "getClassLoader"
白名单 `allowedClassNames` 如下:
0 = "net.sf.hibernate.proxy.HibernateProxy"
1 = "java.lang.reflect.Proxy"
2 = "net.java.ao.EntityProxyAccessor"
3 = "net.java.ao.RawEntity"
4 = "net.sf.cglib.proxy.Factory"
5 = "java.io.ObjectInputValidation"
6 = "net.java.ao.Entity"
7 = "com.atlassian.confluence.util.GeneralUtil"
8 = "java.io.Serializable"
此外,对成员函数等也进行了检查( `containsUnsafeExpression` 函数 ):
小伙伴看到这里,应该很容易想到多种绕过的方法,感兴趣的小伙伴可以进入漏洞空间站进行交流。其中一种实现命令执行结果回显的方式如下(适用全部受影响的版本):