Jenkins CVE-2018-1000861漏洞深度剖析:从Stapler框架到Groovy沙箱绕过

发布时间:2026/7/1 23:09:36
Jenkins CVE-2018-1000861漏洞深度剖析:从Stapler框架到Groovy沙箱绕过 1. 项目概述一次对Jenkins核心安全机制的深度剖析最近在整理历史高危漏洞的复现与分析笔记翻到了CVE-2018-1000861这个案例。这是一个典型的、影响范围极广的Jenkins远程代码执行漏洞其根源在于Jenkins处理用户请求参数时的逻辑缺陷。对于安全研究人员和运维工程师来说理解这个漏洞的成因不仅能帮助我们更好地评估自身系统的风险更能深刻理解Java Web应用安全、Groovy脚本沙箱绕过以及反序列化攻击链的构造思路。这篇文章我将从一个实战者的角度带大家从源码层面拆解这个漏洞并手把手完成一次完整的本地复现。整个过程不涉及任何攻击性行为纯粹是出于技术研究和防御加固的目的。无论你是想深入了解Jenkins安全机制还是希望提升自己的代码审计能力这篇详尽的复盘都能给你带来直接的帮助。2. 漏洞背景与核心原理拆解2.1 Jenkins与Groovy沙箱机制简介Jenkins作为一个用Java编写的开源持续集成工具其强大之处在于高度的可扩展性这主要依赖于其插件体系和内置的脚本控制台。为了允许管理员执行一些系统管理任务Jenkins提供了“脚本控制台”功能允许用户直接在后端服务器上执行Groovy脚本。这无疑是一个极其危险的功能因此Jenkins引入了Groovy沙箱Groovy Sandbox机制。简单来说沙箱机制就像给脚本执行套上了一个“紧箍咒”。它通过一个安全策略文件通常是script-security插件管理的来定义一系列规则规定哪些Java类、哪些方法可以被脚本调用。例如默认情况下脚本不允许直接执行Runtime.getRuntime().exec(“whoami”)这样的命令因为java.lang.Runtime类的exec方法在沙箱的黑名单或未在白名单中。沙箱会在脚本执行前或执行过程中进行拦截和检查一旦发现违规操作就抛出org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException异常并终止执行。2.2 CVE-2018-1000861漏洞的根源Stapler框架与动态路由Jenkins使用了一个名为Stapler的Web框架来处理HTTP请求到后端Java对象方法的映射。Stapler的一个特点是支持“动态方法分发”。举个例子如果一个Java对象有一个名为doIndex的方法那么访问/context/object/这个URL时Stapler就会尝试调用这个doIndex方法。漏洞的核心就藏在Stapler框架处理请求参数与对象属性绑定的过程中。具体来说是org.kohsuke.stapler.RequestImpl类中的bindParameters方法以及与之相关的setter方法调用逻辑。当Jenkins接收到一个HTTP请求时Stapler会尝试将请求参数来自URL查询字符串或POST表单绑定到对应的Java对象属性上。这个过程通常是通过调用对象的setter方法即setXxx格式的方法来完成的。然而这里存在一个关键问题Stapler在寻找并调用setter方法时其查找逻辑过于“宽容”。它不仅仅会查找严格的setProperty方法还会尝试处理一些特殊格式的参数名并最终可能调用到对象的任意公共方法而不仅仅是标准的setter。2.3 漏洞触发链从参数绑定到代码执行攻击者是如何利用这个宽松的绑定逻辑来实现远程代码执行的呢整个攻击链可以概括为以下几个关键步骤寻找可利用的“跳板”对象攻击者需要找到一个在请求处理上下文中可访问的Java对象该对象拥有一个方法该方法能间接导致代码执行。在CVE-2018-1000861中这个关键对象是hudson.model.Descriptor。Descriptor是Jenkins中用于描述插件或扩展点配置的元数据对象。利用动态绑定调用非标准SetterDescriptor类有一个getProperty方法用于读取其属性。相应地攻击者可以构造一个特殊的参数名诱使Stapler去调用一个名为setProperty的方法。即使Descriptor没有明确定义setProperty方法Stapler的绑定逻辑也可能被欺骗。关键方法Descriptor.setProperty研究发现通过特定的参数构造例如source可以实际调用到Descriptor.setProperty(String, Object)方法。这个方法的作用是设置Descriptor的某个属性值。属性值可控导致沙箱绕过setProperty方法的第二个参数value直接来自于用户可控的请求参数。最关键的是这个value参数的类型是Object。在Groovy中字符串“{代码}”可以被解析为闭包Closure。当这样一个闭包字符串作为Object传递给某些特定的处理流程时例如在Groovy沙箱的某些检查环节可能会被误认为是“安全”的脚本片段从而逃逸沙箱的限制。最终执行当这个被“误放行”的闭包在后续的上下文可能是在一个沙箱检查不严格的脚本解析环节中被求值evaluated时其中包含的恶意代码如命令执行就会被执行从而完成从远程参数输入到服务器端代码执行的完整攻击链。注意以上描述是对复杂交互过程的高度简化。实际漏洞利用链涉及Stapler、DataBoundSetter注解处理、Groovy脚本解析等多个组件的交互瑕疵其根本原因是整个参数绑定和传递路径上对用户输入的控制和沙箱检查出现了断层导致不可信数据流入了可信的执行环境。3. 漏洞复现环境搭建与调试3.1 环境准备与目标选择为了安全且清晰地研究漏洞我们需要在本地搭建一个受控的漏洞环境。我推荐使用Docker这能保证环境的独立性和可重复性分析完毕后一键销毁不会影响宿主机。首先我们需要确定存在漏洞的Jenkins版本。根据漏洞公告CVE-2018-1000861受影响的版本范围是Jenkins核心版本2.153及更早以及LTS 2.138.3及更早。我们选择一个明确的、受影响的版本进行复现例如jenkins/jenkins:2.138-slim这个Docker镜像。操作步骤如下启动漏洞版本的Jenkins容器docker run -d --name jenkins-cve-2018-1000861 -p 8080:8080 -p 50000:50000 jenkins/jenkins:2.138-slim这条命令会在后台启动一个名为jenkins-cve-2018-1000861的容器将宿主机的8080端口映射到容器的8080端口Jenkins Web界面50000端口用于可能的代理通信。获取初始管理员密码 Jenkins第一次启动需要解锁。查看容器日志获取初始密码docker logs jenkins-cve-2018-1000861在输出日志中你会找到一行类似这样的信息************************************************************* ************************************************************* ************************************************************* Jenkins initial setup is required. An admin user has been created and a password generated. Please use the following password to proceed to installation: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx This may also be found at /var/jenkins_home/secrets/initialAdminPassword ************************************************************* ************************************************************* *************************************************************复制这串密码xxxxxxxx...。完成基础安装 在浏览器中访问http://localhost:8080。输入上一步复制的初始管理员密码。在“自定义Jenkins”页面选择“安装推荐的插件”或“选择插件来安装”。为了简化环境我们可以先只安装最少的插件甚至跳过插件安装在后续步骤中我们需要安装“Script Security”插件但旧版本可能已内置。对于漏洞复现而言确保script-security插件存在即可版本需与漏洞条件匹配。创建第一个管理员用户或者直接使用初始密码继续作为admin。3.2 源码获取与IDE配置为了深入理解漏洞我们需要查阅对应的Jenkins和Stapler源码。Jenkins项目在GitHub上开源我们可以使用Git来获取特定版本的代码。克隆Jenkins仓库并切换到漏洞版本git clone https://github.com/jenkinsci/jenkins.git cd jenkins # 查找与2.138对应的tag或commit例如 git checkout jenkins-2.138由于版本久远可能需要一些时间克隆和切换。导入IDE进行分析 我习惯使用IntelliJ IDEA。将项目导入为Maven项目IDEA会自动下载依赖。关键是要能方便地进行代码跳转和搜索。我们关注的核心包是org.kohsuke.staplerStapler框架源码通常在依赖中可能需要单独克隆stapler仓库hudson.modelJenkins核心模型包含Descriptor类org.jenkinsci.plugins.scriptsecurity.sandbox脚本安全沙箱插件源码如果网络环境下载依赖较慢可以考虑配置Maven国内镜像源。将源码导入后你可以通过搜索bindParameters、setProperty、Descriptor等关键词来定位相关代码。3.3 调试环境搭建可选但推荐如果你想动态跟踪请求处理流程可以配置远程调试。以调试模式启动Jenkins容器 需要修改Docker启动命令添加JVM调试参数并暴露调试端口例如5005。docker run -d --name jenkins-debug \ -p 8080:8080 \ -p 50000:50000 \ -p 5005:5005 \ -e JAVA_OPTS-agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 \ jenkins/jenkins:2.138-slim参数解释-agentlib:jdwp开启JPDA调试servery以服务模式监听suspendn启动时不挂起如果想在启动时就断点可设为yaddress*:5005监听所有IP的5005端口。在IDE中配置远程调试在IntelliJ IDEA中点击Run-Edit Configurations。点击选择Remote JVM Debug。设置一个名称如Jenkins Remote Debug。Host填写localhostPort填写5005。点击OK保存。开始调试 启动这个远程调试配置点击Debug按钮IDEA会连接到Docker容器中的Jenkins JVM。此时你可以在源码中例如RequestImpl.bindParameters方法打上断点然后在浏览器中访问Jenkins并触发漏洞利用请求程序执行到断点处就会暂停你可以查看此时的调用栈、变量值等信息这对于理解漏洞流转路径至关重要。4. 漏洞利用链的详细分析与复现操作4.1 利用POC构造与理解网络上已经公开了该漏洞的利用代码Proof of Concept, POC。我们不直接使用现成的攻击工具而是通过分析POC的HTTP请求来理解其原理。一个典型的POC请求可能如下所示POST /descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript HTTP/1.1 Host: target-jenkins:8080 Content-Type: application/x-www-form-urlencoded ... 其他头部 ... sandboxtruevaluepublic class x { public x(){ whoami.execute() } }但这只是最终的表现形式。CVE-2018-1000861的利用链更为迂回。真正的利用链通常通过/securityRealm等端点利用Stapler的参数绑定漏洞将恶意Payload注入到某个Descriptor的属性中最终在Groovy脚本检查或执行环节触发。一个更贴近漏洞根源的测试思路用于本地验证找到可利用的Descriptor我们需要找到一个可以通过Web访问的Descriptor对象。Jenkins中许多管理页面都对应着Descriptor。可以通过查看Jenkins的“系统管理” - “插件管理” - “高级”页面等地方的源代码或者使用浏览器开发者工具观察网络请求来发现对/descriptorByName/...的调用。构造属性注入参数根据漏洞分析关键是通过source等参数名调用到setProperty方法。但由于漏洞已被修复且利用链复杂直接复现原始Payload需要精确还原旧版本的环境和类路径。在公开的漏洞详情中常会提到利用org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript这个类的Descriptor。实操心得在实际的漏洞复现研究中我强烈建议使用已经集成好的、用于安全研究的漏洞环境镜像例如某些Vulhub或VulnApp项目中的环境而不是从零开始配置一个原始的Jenkins。因为这些研究环境通常已经设置好了所有必要的插件版本和配置甚至预置了触发漏洞的脚本可以让你把精力集中在漏洞原理分析上而不是耗费在繁琐的环境依赖问题上。例如你可以搜索针对CVE-2018-1000861的docker复现环境。4.2 本地复现操作步骤基于研究环境假设我们使用一个预配置好的漏洞环境。启动漏洞环境根据你找到的漏洞环境项目如Vulhub中Jenkins相关CVE的目录使用docker-compose up -d启动服务。访问Jenkins浏览器打开http://your-ip:8080。你可能需要等待Jenkins初始化完成。执行复现脚本或发送恶意请求如果环境提供了现成的Python POC脚本运行它即可。脚本通常会自动探测并利用漏洞。如果你想手动验证可以使用curl或Burp Suite重放攻击请求。一个简化的、用于原理演示的请求可能形如注意这不是唯一或最原始的利用方式且因修复可能已失效# 这是一个概念性示例真实有效的Payload复杂得多 curl -X POST http://localhost:8080/securityRealm/user/admin/descriptorByName/... \ --data-urlencode sourceGSTRING_PAYLOAD其中GSTRING_PAYLOAD是经过精心构造的Groovy GString其中嵌入了命令执行代码。验证漏洞如果漏洞利用成功POC脚本可能会执行一个如whoami、id或calc.exeWindows的命令并回显结果。在复现环境中你可能会看到在Jenkins服务器上创建了一个新文件、执行了睡眠命令或者通过DNS请求外带数据。4.3 关键代码节点分析让我们回到源码定位几个关键节点理解漏洞如何从参数传递到代码执行。这需要你结合导入的源码和调试信息进行。org.kohsuke.stapler.RequestImpl#bindParameters 这是请求参数绑定的入口。你可以在此方法内设置断点观察它如何遍历请求参数并尝试调用目标对象的方法。重点关注它如何处理非标准的参数名以及如何解析和准备要传入方法的参数值。hudson.model.Descriptor#setProperty 这是攻击链中的一个关键方法。查看它的实现看它如何处理传入的name和value。注意这个value是Object类型意味着它可以接受字符串、数字、列表也可能接受Groovy闭包表达式。Groovy沙箱的检查点 搜索org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox相关的run或evaluate方法。漏洞的本质是恶意代码绕过了这些检查点。你需要分析在正常的脚本控制台执行流中沙箱是如何介入的而在漏洞利用流中这个检查为什么失效了是因为绑定过程跳过了沙箱检查还是因为Payload的构造方式欺骗了检查器一个常见的沙箱绕过手法是使用Groovy的GString模板字符串特性。GString在求值时会动态计算${...}中的表达式。如果沙箱在静态分析阶段未能正确识别GString中嵌套的恶意代码而在动态执行时又未进行二次检查就可能产生绕过。5. 漏洞修复方案与安全启示5.1 官方修复措施分析Jenkins官方在后续版本中修复了此漏洞。修复的核心思路是收紧Stapler框架的参数绑定逻辑并加强对Descriptor.setProperty等敏感方法的调用控制。Stapler框架的修补对RequestImpl.bindParameters或相关方法进行了修改严格限制了哪些方法可以被通过参数绑定方式调用特别是对于非标准的setter方法。可能增加了注解检查如DataBoundSetter或显式的安全许可检查。脚本安全插件的增强script-security插件可能更新了其沙箱规则修补了导致特定Groovy构造如某些GString用法被误判为安全的逻辑缺陷。同时对通过非脚本控制台途径传入的Groovy代码片段实施了更严格的沙箱检查。访问控制的强化确保即使在某些绑定路径下调用敏感操作也需要相应的Permission检查而不仅仅是依赖沙箱。查看修复版本的commit diff是学习安全编码的最佳实践。你可以在Jenkins的GitHub仓库中对比漏洞版本如2.138和修复后版本如2.139的代码差异重点关注stapler模块和jenkins-core模块中相关类的改动。5.2 对开发与运维的安全启示CVE-2018-1000861给所有开发和运维人员上了一堂深刻的安全课永不信任用户输入这是安全的第一原则。任何来自外部的数据HTTP参数、头部、文件内容等在进入核心业务逻辑或敏感操作如反射调用、脚本执行、反序列化之前都必须经过严格的验证、过滤和转义。在本漏洞中用户控制的参数未经充分检查就被传递到了方法调用和脚本解析环节。沙箱不是银弹沙箱机制非常复杂实现一个安全的沙箱极其困难。它需要精确控制允许访问的类、方法甚至字段。任何微小的逻辑漏洞或设计疏忽都可能导致沙箱被绕过。不能因为有了沙箱就放松对输入验证和访问控制的要求。最小权限原则Jenkins的脚本控制台功能本身就是一个高权限操作。在生产环境中必须严格限制拥有“Administer”或“Run Scripts”权限的用户数量。对于非必需的功能应考虑禁用。及时更新与漏洞扫描对于Jenkins这类核心基础设施必须建立严格的补丁管理流程及时关注安全公告并升级到已修复的安全版本。同时可以使用专门的SCA软件成分分析工具或漏洞扫描器定期对Jenkins实例及其插件进行扫描。深度防御除了升级Jenkins本身还应在网络层、主机层实施深度防御。例如将Jenkins部署在内网严格限制外网访问在Jenkins服务器上部署主机安全Agent监控异常进程创建和网络连接对Jenkins的日志进行集中分析和告警监控异常访问模式。5.3 针对该漏洞的临时缓解措施如果无法立即升级在官方修复版本发布后但尚未升级的窗口期可以考虑以下缓解措施禁用脚本控制台可以通过设置系统属性-Dhudson.remoting.ClassFilterorg.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript来尝试限制但最根本的是在“全局安全配置”中将“脚本命令行”的执行权限分配给绝对可信的用户组或者完全移除相关权限。注意禁用脚本控制台可能影响某些管理功能。网络隔离确保Jenkins管理界面不直接暴露在公网通过VPN或跳板机访问。强化访问控制使用Jenkins强大的基于角色的权限管理插件如Role-based Authorization Strategy为不同用户分配最小必要权限。6. 漏洞复现与研究中的常见问题在搭建环境和分析漏洞的过程中你可能会遇到以下问题Docker容器启动失败或Jenkins无法访问端口冲突确保宿主机8080、50000端口未被占用。可通过netstat -tulnp | grep :8080检查。权限问题Docker容器内Jenkins默认以jenkins用户UID 1000运行如果挂载了宿主机卷需确保目录权限正确。资源不足Jenkins启动需要一定内存确保Docker引擎分配了足够资源如至少512MB内存。源码编译或依赖下载失败Maven仓库问题配置~/.m2/settings.xml使用国内镜像源如阿里云、腾讯云镜像。版本太旧某些远古版本的依赖可能已从中央仓库移除需要寻找替代仓库或手动安装。JDK版本确保使用与Jenkins版本兼容的JDK如JDK 8进行编译。断点调试不生效端口未映射或防火墙确认Docker启动命令正确映射了调试端口如5005且宿主机防火墙允许该端口连接。源码版本不匹配确保IDE中打开的源码版本与容器中运行的Jenkins版本完全一致否则行号对不上断点可能无效。JVM参数未生效检查容器日志确认JAVA_OPTS环境变量已被正确加载并输出在启动日志中。POC利用不成功环境不纯净你搭建的环境可能缺少必要的插件或者插件版本与漏洞利用条件有细微差别。强烈建议使用社区维护的、已验证的漏洞环境。Payload构造问题原始POC可能针对特定小版本需要根据你的环境微调。理解原理后可以尝试自己构造。利用Burp Suite的Intruder功能对Payload进行模糊测试有时会有意外收获。已内置修复确认你使用的镜像或War包版本确实在受影响范围内。有时镜像的标签可能不准或者基础镜像已包含了某些背端口修复。漏洞原理难以理解从动态调试入手如果静态读代码感到困惑一定要结合动态调试。在关键函数入口设断点单步跟踪整个请求处理流程观察变量值的变化这是理解复杂交互逻辑的最有效方式。查阅公开分析文章安全社区如Seebug、先知、安全客等通常有研究员发表详细的分析文章结合多篇文章的观点可以帮助你形成更全面的认识。简化问题尝试自己编写一个最简单的、模拟Stapler参数绑定和Groovy执行的Java程序剥离Jenkins庞大的框架专注于核心漏洞点这有助于厘清主线逻辑。研究历史漏洞的价值不在于“利用”它而在于理解攻击者的思维、学习防御者的方案并将这些经验应用到我们自身系统的设计和代码审查中。每一次对漏洞的深度剖析都是对自身安全能力的一次有效锤炼。