前言
CVE-2024-21683 是一种在 Confluence 数据中心和服务器中发现的远程代码执行(RCE)漏洞。Confluence 是由 Atlassian 开发的一款流行协作工具,广泛应用于知识管理、文档制作和团队协作。它既可以作为云服务使用,也可作为企业的自托管解决方案。
该漏洞存在于使用 RhinoLanguageParser 类的 Confluence 版本中,该类在脚本环境中错误地暴露了 Java 类,从而导致潜在的远程代码执行。该漏洞在用户控制的输入通过 Rhino 脚本引擎进行评估时会被激活,这使得攻击者能够注入任意代码。
什么是 Confluence?
Confluence 是一款由 Atlassian 开发的基于网络的协作工具,主要用于知识管理、文档制作和团队协作。它允许团队在一个集中空间中创建、分享和协作内容,例如项目文档、会议记录和流程指南。凭借实时编辑、任务管理功能以及与其他 Atlassian 工具(如 Jira)的集成功能,Confluence 帮助团队组织信息、简化工作流程并改善沟通。同时,它提供了云服务和企业自托管解决方案,以适应不同组织的需求。
实验环境设置
为了搭建实验环境,需要下载一个易受攻击的 Confluence 版本。在本实验中,我使用的是 8.9.0 版本:
下载应用
wget https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-8.9.0.tar.gz
解压文件
tar -xvzf atlassian-confluence-8.9.0.tar.gz
添加调试选项(适用于 Linux) 在 bin/setenv.sh 文件的末尾添加以下内容:
CATALINA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1337 $CATALINA_OPTS"
添加调试选项(适用于 Windows)
set CATALINA_OPTS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:1337 %CATALINA_OPTS%
运行应用
sudo ./start-confluence.sh
补丁对比
对于补丁对比,我们将使用 8.9.0(易受攻击版本)和 8.9.1(未受攻击版本)。如这篇简短说明中提到的,改动发生在 RhinoLanguageParser 类中。在与 initStandardObjects 一起使用时,如果攻击者能够控制以下调用中的 script 变量,则可能导致远程代码执行:
cx.evaluateString(scope, script, “ParserScript”, 0, (Object)null);
我们可以在路径 confluence/WEB-INF/atlassian-bundled-plugins/com.atlassian.confluence.ext.newcode-macro-plugin-x.x.x.jar下找到RhinoLanguageParser类。因此,我们获取了两个 JAR 文件,其中一个来自易受攻击版本,一个来自已修补版本,以比较它们之间的差异。
在 IntelliJ 中,我们创建一个新项目,然后导航到 文件 > 项目结构:
接下来,选择“新建项目库”并添加这两个 JAR 文件。
最后,点击“应用”和“确定”。之后,可以在左侧的外部库中找到这两个文件:
选择两个文件并按下 CTRL+D 来执行文件的差异比较。
当检查两个版本中的 RhinoLanguageParser 类时,可以确认所做的更改:
该更改是通过将 initStandardObjects 的使用替换为 initSafeStandardObjects 来实现的。
漏洞分析
让我们分析补丁中使用的函数,逐步了解每个函数中发生的事情。
public static ScriptableObject initSafeStandardObjects(Context cx, ScriptableObject scope, boolean sealed) {
if (scope == null) {
scope = new NativeObject();
} else if (scope instanceof TopLevel) {
((TopLevel) scope).clearCache();
}
scope.associateValue(LIBRARY_SCOPE_KEY, scope);
new ClassCache().associate(scope);
BaseFunction.init(scope, sealed);
NativeObject.init(scope, sealed);
Scriptable objectProto = ScriptableObject.getObjectPrototype(scope);
Scriptable functionProto = ScriptableObject.getClassPrototype(scope, “Function”);
functionProto.setPrototype(objectProto);
if (scope.getPrototype() == null) {
scope.setPrototype(objectProto);
}
NativeError.init(scope, sealed);
NativeGlobal.init(cx, scope, sealed);
NativeArray.init(scope, sealed);
if (cx.getOptimizationLevel() > 0) {
Native