针对 CVE-2024-23897 的完整自包含本地复现,这是一个 Jenkins CLI 中的严重(CVSS 3.1 基础分 9.8)任意文件读取漏洞。该项目启动两个 Docker 堆栈,它们仅在 Jenkins 次要版本上有所不同,对两者运行相同的概念验证,并展示漏洞在未修补的控制器上触发,而在已修补的控制器上静默。
一切都由一个 Python 程序 poc.py 驱动。测试工具仅编排环境(Docker Compose、官方的 jenkins-cli.jar 客户端,并逐字捕获输出)。漏洞本身存在于 Jenkins 自己的 Java 代码中,并未在此重新实现。
完整的书面分析,包括补丁审查和 CVSS 分解,见 report/report.pdf。
Jenkins CLI 使用 args4j 库构建其参数解析器。args4j 有一个名为 expandAtFiles 的特性,由 atSyntax 标志控制并默认启用,该特性在命令运行之前将任何形式为 @/path/to/file 的参数重写为该文件的内容。该文件以 Jenkins 控制器进程的权限打开。由于所有三个 CLI 传输(HTTP、WebSocket、SSH)都汇入同一个解析器,任何可以发送任意 CLI 命令的客户端都可以读取控制器上的任意文件。修复(提交 554f0378)添加了一个常量 ALLOW_AT_SYNTAX,其默认值为 false,从而关闭了扩展。
这不是经典的路径遍历:没有 ../,也没有要逃逸的基本目录。路径直接被打开。在结果轴上是任意文件读取;在机制轴上是参数扩展。
cve-2024-23897-jenkins-poc/
README.md # 本文件
LICENSE
poc.py # Python 复现测试工具(所有子命令)
Dockerfile.vuln # jenkins/jenkins:2.426.2-lts + matrix-auth
Dockerfile.fix # jenkins/jenkins:2.426.3-lts + matrix-auth
docker-compose.vuln.yml # jenkins-vuln + attacker-vuln
docker-compose.fix.yml # jenkins-fix + attacker-fix
init.groovy.d/
01-create-users.groovy # 通过 matrix-auth 引导 admin 和 readuser
evidence/
docker-versions.txt # 主机 Docker 和 Compose 版本
output-vulnerable.txt # 在 `poc.py exploit` 期间捕获
output-fixed.txt # 在 `poc.py verify-fix` 期间捕获
report/
report.pdf # 完整的书面分析
report.tex # LaTeX 源(自包含,无外部图片)
| 要求 | 备注 |
|---|---|
| Docker Engine 24 或更新版本 |
主机上不需要 JDK。Java 在攻击者容器内运行。compose 文件固定了 platform: linux/amd64,因此镜像在 Apple Silicon 上表现相同;这是一个操作选择,并未触及漏洞,该漏洞与平台无关。
在仓库目录内:
python3 poc.py up-vuln # 构建并启动易受攻击的堆栈,获取 CLI jar
python3 poc.py place-proof # 在控制器内写入无害的标记文件
python3 poc.py exploit # 运行 PoC,写入 evidence/output-vulnerable.txt
python3 poc.py up-fix # 拆除 vuln,构建并启动已修补的堆栈
python3 poc.py verify-fix # 运行相同的 PoC,写入 evidence/output-fixed.txt
python3 poc.py teardown # 停止并移除两个堆栈
首次运行需要几分钟(拉取镜像加上安装插件)。后续运行快得多。
当捕获的输出中出现标记字符串 POC-PROOF-LINE(泄漏发生)时,poc.py exploit 通过。poc.py verify-fix 则在相反条件下通过:标记必须不存在。两个子命令在失败时均以非零退出,因此两个证据文件及其退出状态本身就是测试结果。
两个 compose 文件在同一 Docker 网络上运行两个服务:一个 Jenkins 控制器(受害者)和一个小的 eclipse-temurin:17-jre 攻击者容器。poc.py 在主机上运行,通过 subprocess 驱动 Docker,但实际的 java -jar jenkins-cli.jar ... 调用在攻击者容器内运行。攻击者无法访问 Jenkins 数据卷;它仅通过网络到达控制器,就像远程攻击者一样。
init.groovy.d/01-create-users.groovy 使用 matrix-auth 插件创建两个具有明显不同权限的帐户:
| 帐户 | 权限 | 在 PoC 中的角色 |
|---|
这复现了官方公告中的确切划分,即 Overall/Read 与匿名,而不是 Jenkins 核心默认产生的较宽松的“任何登录用户与匿名”。因此,完整的文件泄漏仅归因于 CVE 本身,而非管理权限。
PoC 对每个控制器运行四种上下文。下表总结了 A/B 比较;完整捕获见 evidence/。
两列之间变化的唯一变量是 Jenkins 次要版本,进而通过补丁后 atSyntax 的默认值发生变化。因此,相反的结果将行为改变归因于提交 554f0378 中的解析器修复。
在容器中运行 Jenkins 并非缓解措施。解析器以 Jenkins JVM 的权限读取文件,而这些文件位于与凭据存储相同的容器文件系统内。容器边界保护主机免受 Jenkins 进程的影响,而不是保护 Jenkins 进程自身。此处的 Docker 设置仅是一个演示沙箱,除此之外别无他用。
升级到已修补的版本:2.442(weekly),或 2.426.3 或 2.440.1(LTS),均于 2024 年 1 月 24 日发布。如果无法立即升级,请保持系统属性 hudson.cli.CLICommand.allowAtSyntax 未设置(其默认值),这会使 ALLOW_AT_SYNTAX 保持为 false 并禁用扩展。禁用单个 CLI 传输仅是部分措施,因为所有三个传输都汇聚到同一个解析器。
所有工作都在本地 Docker 环境中进行,针对一个无害的三行标记文件(/tmp/poc-proof.txt),该文件由测试工具自行创建。不会扫描或联系任何公共 Jenkins 实例,也不会读取任何真实的秘密,例如 secrets/master.key 或 credentials.xml。
554f0378:https://github.com/jenkinsci/jenkins/commit/554f03782057c499c49bbb06575f0d28b5200edbParserProperties.withAtSyntax):https://github.com/kohsuke/args4j使用的确切版本记录在 evidence/docker-versions.txt 中 |
| Docker Compose v2 | 以内置 docker compose 插件形式提供 |
| Python 3.8 或更新版本 | 仅标准库,无需 pip install |
| 磁盘空间 | 约 1.5 GB,用于两个 Jenkins 镜像、temurin 镜像和 matrix-auth 插件 |
admin | Jenkins.ADMINISTER | 仅用于满足“至少一个管理员”,从不用于攻击 |
readuser | 仅 Jenkins.READ(Overall/Read) | 经过身份验证的攻击者 |
| 匿名 | 无 | 未经身份验证的攻击者 |
| 观察项 | 易受攻击的 2.426.2 | 已修补的 2.426.3 |
|---|
@ 令牌处理 | 扩展为文件内容 | 视为字面字符串 |
readuser + connect-node | 完全文件泄露(3 行中的 3 行) | 无泄露 |
匿名 + who-am-i / help | 通过验证门前的解析器错误部分泄露(第一行) | 无泄露 |
输出中的标记 POC-PROOF-LINE | 存在 | 不存在 |