Confluence环境搭建及漏洞分析

这篇笔记是一个多月前写的,本来是想分析漏洞 CVE-2023-22522,但当时关于该漏洞的信息太少,最后也没分析出该漏洞原貌。笔记只留下一些环境搭建的方法和分析思路,另外还有一个不太算漏洞的漏洞,官方给了$300意思意思

环境搭建

下载地址:https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-8.5.3.zip

支持的JDK和数据库版本:https://confluence.atlassian.com/doc/supported-platforms-207488198.html

docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
confluence:
build: .
ports:
- "8090:8090"
- "5005:5005"
environment:
- CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
depends_on:
- postgres
command: /atlassian-confluence/bin/start-confluence.sh --fg

postgres:
image: postgres:15-alpine3.18
expose:
- 5432
environment:
- POSTGRES_PASSWORD=postgres
command:
- sh
- -c
- echo 'CREATE DATABASE confluence;' > /docker-entrypoint-initdb.d/init.sql && /usr/local/bin/docker-entrypoint.sh postgres

Dockerfile:

1
2
3
4
5
6
7
8
9
10
FROM openjdk:17-jdk-bullseye

COPY atlassian-confluence-8.5.3.zip /
COPY confluence.cfg.xml /var/data/confluence/confluence.cfg.xml

RUN unzip atlassian-confluence-8.5.3.zip && \
mv atlassian-confluence-8.5.3 atlassian-confluence && \
mkdir -p /var/data/confluence/ && \
echo ' ' >> /atlassian-confluence/confluence/WEB-INF/classes/confluence-init.properties && \
echo 'confluence.home=/var/data/confluence/' >> /atlassian-confluence/confluence/WEB-INF/classes/confluence-init.properties

confluence.cfg.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>

<confluence-configuration>
<setupStep>setupcluster-start</setupStep>
<setupType>custom</setupType>
<buildNumber>9012</buildNumber>
<properties>
<property name="access.mode">READ_WRITE</property>
<property name="atlassian.license.message">AAABlA0ODAoPeNp1klFvmzAUhd/9K5D20qlya0MXIJKlUSBZOkLaQlJlyotrXRK3QCID6divn1Mnq lq1j77n+Pq75/pbvumsCIRFXIv6Q5sMnSsrzHLLJraDQgW8lds64i2wQwVTGxMPRdAIJXcHiYXbu ig7qAVYZ9rHrRDqFtT31dCK97zsXhugKZe6WnNti//upOpPLa8woZi4KJEC6uYLsTTiAlRzeNJGW ff4RtCqDpDQGBdpVz2CmhXzRjsZpuiNwLhmas1r2ZhK3/W8Xj9JXv5cV1yWF2JbmT5ctHIP5oqer 9XnWE9Qfn7lSPeLNxs2DV/CUewv/z25Fb07zwux9MejYMQj+1n8qUWwuV+353Y+rpaDKP79wB+8Y lG9JEEaXo7vVmzFDMExjrzfQcorYOFsOo3vw0mQGD1rudIps4KXDZzCm0QsmURZnOKE+p47oD8cl IHag9LKtR/McbrIr3Gcux6+8cjctDoszezMDPwM/SloOiDEJZ7jUHTbKbHhDXz8Csd4XiE/TUfjs PdI/wGum893MCwCFEBm/Dhfacgh6q8z1KkLA7IoRtgMAhR3aSuUYcM2Bt0xGXVKCB533E+uyg==X 02jj</property>
<property name="confluence.setup.server.id">B9AU-NVTB-ET78-J80U</property>
<property name="confluence.webapp.context.path"></property>
<property name="jwt.private.key">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</property>
<property name="jwt.public.key">MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAkP0PJ779cjmR3XA3dE3DPloM79asNOd4pjQY9lmsUpg7b//3F4ugIBLvIouF44m8/TMjgteMYhh7EPj1jch7JV6uMilWb4POdbgod5zUlHESnExqfZ0tHSFsiYFVJCBgq3dzsMaSkDxxLKBYiua+wJ8AThXdNuclDb7FK+vZC2IRgA+KwQGOCbEVhUP1qbpwpBTAiD62j7HnKsBcTd0WApiNq6Q0dPAZUH0lWkNwQkTbU58R29AicgD9Twwmi3A5fLytlk9P+/kNZS9CiklPTdicq/1xzuaQedYZsZQEBiz0jos2VpVqwZYSPcPUxUns1t64rXG3iqhlSUc8Rdr+oy6smgERX9WziQHcRmG7hwreZofRzfFddYR8q3N5QC/Rhj5KAcO9o1B0FXyYojURAkxviyVHvA15mf9z0P7oFneVMIEEJSixtUEhswKXUBQmr07t7SX9LIvwd98mNyipECG3uPrdY6hQzD2bFwfvreFSJlXiTc5cRQmgYLItQw4TAgMBAAE=</property>
<property name="lucene.index.dir">${localHome}/index</property>
<property name="struts.multipart.saveDir">${localHome}/temp</property>
<property name="synchrony.encryption.disabled">true</property>
<property name="synchrony.proxy.enabled">true</property>
<property name="synchrony.service.authtoken">fd4915a6ec781b2479100ef98bb9a788</property>
</properties>
</confluence-configuration>

confluence.cfg.xml中的license是在官网申请的,有效期只有一个月,如果过期了需要重新申请

启动环境:

1
docker compose up -d --build 

漏洞分析

动态调试方法

前文的环境搭建已经开启了远程调试端口,如果要进行断点调试的话,可以将所有jar包导入IDEA作为lib,直接到jar包中的类下断点(用反编译后的源码下断点的话行号可能不准,断不到指定位置)

尝试第一次CodeQL扫描

由于没有分析过confluence相关的漏洞,而且关于这个漏洞公开的细节太少,想试试用CodeQL扫一下

confluence是闭源系统,可以用这个工具编译数据库:https://github.com/waderwu/extractor-java

将源码包和依赖包分别拷贝到不同目录(这里我默认将以com.atlassian.confluence.开头的视为confluence本身,其他jar包视为依赖):

1
2
3
mkdir -p atlassian-confluence-8.5.3-src/lib/
find atlassian-confluence-8.5.3/ -type f -name "*.jar" | grep "com.atlassian.confluence." | xargs -I {} cp {} atlassian-confluence-8.5.3-src/
find atlassian-confluence-8.5.3/ -type f -name "*.jar" | grep -v "com.atlassian.confluence." | xargs -I {} cp {} atlassian-confluence-8.5.3-src/lib/

将源码包解压:

1
2
cd atlassian-confluence-8.5.3-src/
ls |grep ".jar" | xargs -I {} unzip -q -n {}

反编译class(只处理包名为com.atlassian.confluence的类):

1
python3 ~/Tools/extractor-java/class2java.py com/atlassian/confluence

编译CodeQL数据库:

1
python3 ~/Tools/extractor-java/run.py atlassian-confluence-8.5.3 com/atlassian/confluence -ld lib/

然后用CodeQl官方的模板注入规则进行扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @name Server-side template injection
* @description Untrusted input interpreted as a template can lead to remote code execution.
* @kind path-problem
* @problem.severity error
* @security-severity 9.3
* @precision high
* @id java/server-side-template-injection
* @tags security
* external/cwe/cwe-1336
* external/cwe/cwe-094
*/

import java
import semmle.code.java.security.TemplateInjectionQuery
import TemplateInjectionFlow::PathGraph

from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sink
where TemplateInjectionFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Template, which may contain code, depends on a $@.",
source.getNode(), "user-provided value"

没想到还真扫到一个可能的漏洞,从com.atlassian.confluence.plugins.gatekeeper.controllers.AbstractPermissionsAction的字段exportCustomSpaceDetailsFormatvelocityEngine.evaluate,理论上exportCustomSpaceDetailsFormat可控的话,就可以造成模板注入漏洞

其实一开始我不知道为什么这个类的字段可以作为source,猜想可能是confluence的web框架会自动注入请求参数

关于入口或者说漏洞接口的寻找思路:

AbstractPermissionsAction是一个抽象类,所以我先找了同级目录下的对应实现类SpacePermissionsAction

SpacePermissionsAction看起来像是设置空间权限的controller,它们所在jar包看起来是一个名为gatekeeper的插件,后续又结合官方文档,推测并找到了一个功能入口:

/plugins/gatekeeper-plugin/space/inspect-permissions.action?key=TEST

点击导出,对应的请求如下:

1
2
3
4
5
POST /plugins/gatekeeper-plugin/space/init-evaluator.action HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/x-www-form-urlencoded

key=TEST&userFilter=&excludeDisabled=false&excludeOwnersNoPermissions=false&alwaysShowAnonymous=false&pageId=&exportCsvDelimiter=%2C&exportSpaceDetailsFormat=key&exportFormat=csv

代码会运行到com.atlassian.confluence.plugins.gatekeeper.controllers.AbstractPermissionsAction#initEvaluator

但漏洞Sink在checkValidVelocitySyntax方法中,于是构造请求:

1
key=TEST&userFilter=&excludeDisabled=false&excludeOwnersNoPermissions=false&alwaysShowAnonymous=false&pageId=&exportCsvDelimiter=%2C&exportSpaceDetailsFormat=custom&exportFormat=csv&exportCustomSpaceDetailsFormat=leixiao

代码按预期走到了漏洞点,但奇怪就在这行代码怎么也执行不了,会抛出类加载冲突的异常:

以为会和Jdk版本有关,尝试了Jdk11和Jdk17,都是这样,所以放弃了这个接口的尝试

AbstractPermissionsAction共有两个实现类:

另一个类名为GlobalPermissionsAction,但对应的模组貌似是被默认禁用的:

开启后该模组后,入口在/admin/plugins/gatekeeper-plugin/global/inspect-permissions.action,同样点击导出,测试后发现也会抛出类加载冲突异常。

后来搜到一个15年的Bug:https://jira.atlassian.com/browse/CONFSERVER-40148 ,和我遇到的问题一致,官方在20年发表的最后一句回复说已经在最新版本的Confluence上解决了,如果再次遇到这个情况可以继续反馈。我理解官方应该是承认这是设计上的Bug,并且会修复。然后刚好gatekeeper这个插件也有这个问题,但是没有被发现,所以目前来看这个插件确实存在着漏洞,但漏洞也确实利用不了。

如果Bug被解决,那么这个漏洞是可以被轻松利用的,首先原本的POST请求其实可以转换成GET请求,然后就有两种利用方式:

  1. 未授权的用户发送链接给管理员使其点击,类似于CSRF
  2. 普通权限用户将链接以图片Url插入文章中,等待管理员浏览

(后续是我本地修复了这个Bug,Bug原因是依赖包冲突,将org.apache.servicemix.bundles.velocity-1.7_6.jar换成maven仓库中的org.apache.servicemix.bundles.velocity-1.6.4_4.jar即可,但修复后依旧没办法利用。。。 黑名单限制太死了,而且此处也没有上下文,实在找不到绕过方法 -_-)

再后面发现可以造成Dos攻击,报告官方后,给了$300奖励

代码Diff,尝试第二次CodeQL扫描

将8.5.3和8.5.4反编译后的com.atlassian.confluence包中的类进行diff

发现com.atlassian.confluence.velocity.ConfigurableResourceManager#loadResource方法中添加了一段对resourceName进行验证的代码:

随后搜了一些资料,发现这个方法也是CVE-2019-3396的关键一环,所以我猜测CVE-2023-22522的原理可能和CVE-2019-3396差不多

然后根据CVE-2019-3396写了CodeQL的查询语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* @id java/confluence-ssti
* @kind path-problem
* @problem.severity error
*/

import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph

// 从CVE-2019-3396总结的source
class MacroExecuteMethod extends Method {
MacroExecuteMethod() {
exists(RefType t |
t.hasQualifiedName("com.atlassian.confluence.macro", "Macro") and
this.getDeclaringType().getASupertype*() = t and
this.hasName("execute") and
this.getNumberOfParameters() = 3
)
}
}

// 测试发现这里作为sink的话无法直接串联起来,所以改为GetTemplateMethod
class GetResourceMethod extends Method {
GetResourceMethod() {
exists(Class c |
c.hasName("ConfigurableResourceManager") and
this.getDeclaringType() = c and
this.hasName("getResource")
)
}
}

class GetTemplateMethod extends Method {
GetTemplateMethod() {
exists(Class c |
c.hasQualifiedName("org.apache.velocity.app", "VelocityEngine") and
this.getDeclaringType() = c and
this.hasName("getTemplate")
)
}
}

class TaintTrackingConfig extends TaintTracking::Configuration {
TaintTrackingConfig() { this = "TaintTrackingConfig" }

override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
or
exists(MacroExecuteMethod macroExecuteMethod |
source.asParameter() = macroExecuteMethod.getParameter(0)
or
source.asParameter() = macroExecuteMethod.getParameter(1)
)
}

override predicate isSink(DataFlow::Node sink) {
exists(Call call | call.getCallee() instanceof GetTemplateMethod |
sink.asExpr() = call.getArgument(0)
)
}
}

from TaintTrackingConfig cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Server-side template injection, depends on a $@.",
source.getNode(), source.toString()

一番分析调试后,又加了如下Sanitizer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override predicate isSanitizer(DataFlow::Node node) {
exists(Call call | call.getCallee().hasName("getSpaceKey") | node.asExpr() = call)
or
exists(Call call | call.getCallee().hasName("getDocId") and call.getNumArgument() = 1 |
node.asExpr() = call.getAnArgument()
)
or
exists(Call call |
call.getCallee().hasName("get") and
call.getNumArgument() = 1 and
call.getArgument(0).(StringLiteral).getValue() = "_template"
|
node.asExpr() = call
) //传_template的利用方法已经在CVE-2019-3396中被修复了
}

最后查询结果如下:

扫描结果一

总共3个结果,但其实后两个结果是一样的,都是传入了theme这个参数,theme会和路径字符串进行拼接,最后传到com.atlassian.confluence.velocity.ConfigurableResourceManager#getResource

对应的功能点为预览 '导航图'宏

HTTP请求为:

1
2
3
4
5
POST /rest/tinymce/1/macro/preview HTTP/1.1
Host: 127.0.0.1:8090
Content-Type: application/json; charset=UTF-8

{"contentId":"360471","macro":{"name":"navmap","body":"","params":{"theme":"navmap-mytheme"},"defaultParameterValue":"test"}}

com.atlassian.confluence.plugins.macros.advanced.NavigationMapMacro#getTemplate

com.atlassian.confluence.velocity.ConfigurableResourceManager#getResource

到这里,其实和CVE-2019-3396的差别就是:CVE-2019-3396能控制整个resourceName,目前找到的点只能控制resourceName中间的一部分,参考https://paper.seebug.org/893/ 中对CVE-2019-3396的分析,那这里肯定是没办法RCE的,而且在拼接theme之前用isSafeTitleForFilesystem方法进行了安全检查,也没办法目录穿越,所以这个点基本没用

扫描结果二

另一个扫描结果的两条路径是SpaceEditDecoratorActionSpaceViewDefaultDecoratorActiondecoratorName字段传入了getTemplate

对应入口为/spaces/viewdefaultdecorator.action?decoratorName=decorators/main.vmd&key=TEST

com.atlassian.confluence.admin.actions.lookandfeel.AbstractDecoratorAction#getTemplateFromResourceLoader

但是在进入getTemplate前,会经过isUnderConfluenceApp方法的检查:

这个方法的关键就是URI.create(decoratorName).isAbsolute(),在java中,就是判断scheme是否为null:

所以如果想像CVE-2019-3396一样传入file:///...http://...是不行的。我也试了强行向getTemplate传入file:///...http://...ftp://...,也是没办法成功的,可能和Tomcat版本有关,或者是Confluence针对这种注入做了某种特定的修复

参考

https://confluence.atlassian.com/pages/viewpage.action?pageId=1319570362

https://paper.seebug.org/884/

https://paper.seebug.org/893/