这篇笔记是一个多月前写的,本来是想分析漏洞 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-bullseyeCOPY 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 import java import semmle.code.java.security.TemplateInjectionQuery import TemplateInjectionFlow::PathGraph from TemplateInjectionFlow::PathNode source, TemplateInjectionFlow::PathNode sinkwhere 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
的字段exportCustomSpaceDetailsFormat
到velocityEngine.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:8090Content-Type : application/x-www-form-urlencodedkey =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请求,然后就有两种利用方式:
未授权的用户发送链接给管理员使其点击,类似于CSRF
普通权限用户将链接以图片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 import java import semmle.code.java.dataflow.FlowSources import semmle.code.java.dataflow.TaintTracking import DataFlow::PathGraph / / 从CVE-2019 -3396 总结的sourceclass 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的话无法直接串联起来,所以改为GetTemplateMethodclass 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 sinkwhere 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:8090Content-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
方法进行了安全检查,也没办法目录穿越,所以这个点基本没用
扫描结果二 另一个扫描结果的两条路径是SpaceEditDecoratorAction
和SpaceViewDefaultDecoratorAction
的decoratorName
字段传入了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/