The lock on the vault door has a second keyhole nobody checked — and every build job already has a key
CVE-2026-70426 is a deserialization filter bypass in the Jenkins Remoting library (versions ≤3384.v60d89463d9e0, except 3355.3357.v931d3c992987), bundled with Jenkins weekly ≤2.575 and LTS ≤2.568.1. Jenkins Remoting serializes Java objects for controller↔agent communication. To prevent gadget-chain attacks, Jenkins enforces the JEP-200 class allowlist during deserialization. However, the Remoting implementation contains a *fallback code path* for resolving classes that never consults the JEP-200 filter. An attacker who can connect as a Jenkins agent, run code on an existing agent, or hold the Agent/Connect permission can send a malicious serialized object through the fallback path, bypassing the allowlist entirely and achieving remote code execution on the Jenkins controller. The exploitable surface is restricted to classes on the Jenkins core classpath that are not on the legacy pre-JEP-200 denylist — plugin dependencies are excluded.
The vendor's CRITICAL / 9.0 rating is broadly justified. The CVSS vector (AV:N/AC:H/PR:N/UI:N/S:C) correctly captures the scope-change from agent to controller and flags high complexity — finding a usable gadget chain on the restricted core classpath is non-trivial. The PR:N component is generous (agent connection secrets or Agent/Connect permission are typically needed), but in the CI/CD threat model this barely matters: build jobs already execute arbitrary code on agents by design. Every PR, every pipeline script, every shared library is code running in the agent context. The attack doesn't require breaking in — it requires escalating *up* from a position the system was designed to give you. Jenkins controllers store source code, deployment credentials, cloud provider secrets, and signing keys. Controller compromise is supply-chain compromise. The vendor severity matches reality.
4 steps from start to impact.
Obtain agent-level execution
- Network path to Jenkins controller's agent listener (TCP/50000 or WebSocket)
- Agent secret / JNLP token, OR ability to submit a build that runs on an agent, OR Agent/Connect permission
- Well-managed Jenkins instances restrict agent connections to known subnets and rotate secrets
- Kubernetes-based agents with ephemeral pods limit persistent agent compromise
- Organizations that disable the TCP agent port in favor of WebSocket reduce rogue-agent surface
AgentConnected events from unknown IPs.Craft a serialized payload targeting the fallback path
- Knowledge of Jenkins core classpath contents
- A viable gadget chain that avoids the pre-JEP-200 denylist
- Understanding of the fallback resolution trigger in Remoting
- No public PoC or known gadget chain has been demonstrated through this path
- The pre-JEP-200 denylist blocks the most common gadget entry points (Commons Collections transformers, etc.)
- Plugin dependencies are explicitly excluded, significantly narrowing the gadget surface
Send the payload over the Remoting channel
- Active Remoting channel between attacker-controlled agent and controller
- Payload triggers the fallback resolution path specifically
- Remoting protocol is binary and not trivially injectable — requires control of an actual agent process or a compatible client
- Agent-to-controller security hardening (if enabled) may restrict certain callable types
Achieve RCE on the Jenkins controller
credentials.xml, secrets/ directory), access all configured SCM repositories, modify build pipelines, inject backdoors into artifacts, pivot to production deployment targets, or exfiltrate secrets stored in Jenkins' encrypted credential store.- Successful gadget chain execution on controller JVM
- Jenkins controller hardened with minimal OS privileges limits post-exploitation lateral movement
- Secrets stored in external vaults (HashiCorp Vault, AWS Secrets Manager) rather than Jenkins credential store reduce blast radius
java spawning bash/cmd); file integrity monitoring on $JENKINS_HOME/secrets/; anomalous outbound connections from the controller.Agent/Connect is granted only to machine identities with rotated JNLP secrets. Remove this permission from human accounts and from 'authenticated' group defaults. This narrows the 'rogue agent connection' vector but does NOT prevent exploitation from code already running on legitimate agents. Deploy within 3 days.bash, cmd.exe, powershell, curl, wget). This is a detection-only control — it does not prevent exploitation but shortens attacker dwell time. Deploy within 3 days.- WAF / reverse proxy in front of the Jenkins web UI — The vulnerability is in the Remoting binary protocol (JNLP4/WebSocket agent channel), not in the HTTP API. A WAF inspecting HTTP traffic will not see or block the malicious serialized payload.
- Jenkins CSRF protection / API token enforcement — These protect the HTTP API surface. The Remoting channel is a separate protocol with its own authentication (agent secrets). CSRF tokens are irrelevant to agent↔controller communication.
- Disabling the Script Console — While a good hardening measure, the Script Console is an HTTP-based feature. This vulnerability operates entirely over the Remoting channel and does not use the Script Console.
- Updating plugins only — The vulnerability is in Jenkins core's bundled Remoting library. Plugin updates do not change the Remoting version. A core/LTS upgrade or the dedicated workaround plugin is required.
The supporting signals.
| In-the-Wild Exploitation | None observed. Not listed in CISA KEV. No campaigns documented by Mandiant, CrowdStrike, or Proofpoint as of 2026-10-07. |
|---|---|
| Proof-of-Concept | No public PoC. No repositories found on GitHub named after CVE-2026-70426. No entries on pocindex.io, ExploitDB, or Nuclei templates. The restricted core-classpath gadget surface makes weaponization non-trivial but not impossible for skilled researchers. |
| EPSS | 0.454% (37th percentile) — low exploitation probability in the next 30 days, reflecting absence of public tooling. |
| CISA KEV | Not listed as of 2026-10-07. |
| CVSS Vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H — 9.0 CRITICAL. Scope-change (S:C) reflects agent→controller escalation. AC:H reflects gadget-chain complexity. PR:N is generous; real-world exploitation requires agent-level access. |
| Affected Versions | Jenkins weekly ≤2.575, LTS ≤2.568.1, Remoting ≤3384.v60d89463d9e0 (except 3355.3357.v931d3c992987). |
| Fixed Versions | Jenkins 2.576 (weekly), LTS 2.568.2. Remoting fix included in both. Standalone Remoting: 3385.vf1123fb_515da_ or later. |
| Internet Exposure | Shodan reports ~1,500–1,600 Jenkins controllers with dashboards exposed; historically up to 78,000 with http.title:"Jenkins". JNLP port 50000 is exposed on Docker deployments by default. Most enterprise controllers are internal-only, but the agent connection surface (TCP/50000 or WebSocket) is the relevant attack surface here. |
| Disclosure & Reporter | Disclosed 2026-08-05 via Jenkins Security Advisory. SECURITY-3911. Reported through the European Commission's Jenkins Bug Bounty Program. Part of an advisory batch covering 21 CVEs (CVE-2026-70426 through CVE-2026-70448). |
| Workaround | Jenkins CERT published a mitigation repository for environments that cannot patch immediately. |
Sources.
- Jenkins Security Advisory 2026-08-05
- GHSA-v269-8r2r-vj93 — GitHub Security Advisory
- Jenkins CERT Workaround — SECURITY-3911-3930
- GBHackers — Critical Jenkins Deserialization Flaw Analysis
- CyberSecurityNews — Jenkins Code Execution Vulnerability
- Feedly CVE Intelligence — CVE-2026-70426
- SentinelOne Vulnerability Database — CVE-2026-70426
- Jenkins Exposed Services Documentation
Why this verdict
- Canonical CI/CD component — floor is CRITICAL. Jenkins controllers are *definitionally* CI/CD infrastructure. 100% of Jenkins installations serve the high-value CI/CD role. A compromised controller yields access to source code, deployment credentials, signing keys, and artifact pipelines — supply-chain-scale blast radius. Per the deployment-role floor rule, the verdict cannot drop below CRITICAL.
- Agent-level access is the normal operating mode, not a barrier. The CVSS vector marks PR:N, and while that's debatable, the practical reality is even stricter: CI/CD systems are *designed* to run developer-submitted code on agents. Every PR build, every pipeline script, every shared library is attacker-controlled code already executing in the agent context. The 'prerequisite' is the system working as intended.
- Role multiplier: CI/CD controller. The documented chain succeeds whenever code runs on a connected agent — this is the standard deployment model. Blast radius on success: controller RCE → credential store theft → SCM access → artifact tampering → supply-chain compromise. This is fleet-to-supply-chain impact. No role in the Jenkins ecosystem is low-value; the controller IS the high-value target.
- AC:H and restricted gadget surface justify the 0.2-point reduction, not more. The gadget chain must use only Jenkins core classpath classes and avoid the pre-JEP-200 denylist. Plugin dependencies are excluded. No public PoC exists. EPSS is 0.454%. These factors reduce *immediacy* but do not reduce *severity* — a skilled researcher or nation-state actor can invest the time to find a chain.
- Role multiplier: identity and secrets store. Jenkins credential stores frequently hold SSH keys, API tokens, cloud IAM credentials, and database passwords. Controller compromise is functionally equivalent to a PAM breach in many organizations. This reinforces the CRITICAL floor.
Why not higher?
The vendor already assigned the maximum reasonable score for an AC:H vector (9.0). The scope-change, full CIA impact, and network vector are all accurately captured. A 10.0 would require AC:L and trivial exploitation, which this vulnerability does not offer — the restricted gadget surface and absence of a public chain are real complexity barriers.
Why not lower?
Jenkins is a canonical CI/CD component where 100% of installs occupy the high-value role. The deployment-role floor rule mandates CRITICAL when the chain succeeds with supply-chain impact, which it does for any installation where agents run build code (i.e., virtually all of them). The lack of a public PoC reduces *urgency* but not *severity* — the vulnerability class (Java deserialization filter bypass) is well-understood, and history shows that JEP-200 bypasses are consistently weaponized within months of disclosure (cf. CVE-2017-1000353, CVE-2024-43044).
Crowdsourced verification payload.
Run this script on each Jenkins controller host (or remotely via SSH). It checks the installed Jenkins version and the bundled Remoting library version. Requires read access to $JENKINS_HOME or the Jenkins WAR file. Invoke as: sudo bash check_cve_2026_70426.sh /var/lib/jenkins (pass the JENKINS_HOME path as the first argument).
#!/usr/bin/env bash
# CVE-2026-70426 Checker — Jenkins Remoting JEP-200 Deserialization Filter Bypass
# Usage: bash check_cve_2026_70426.sh /var/lib/jenkins
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
set -euo pipefail
JENKINS_HOME="${1:-/var/lib/jenkins}"
RESULT="UNKNOWN"
# Try to read Jenkins version from identity.xml or config
get_jenkins_version() {
local id_file="$JENKINS_HOME/config.xml"
if [ -f "$JENKINS_HOME/identity.xml.bak" ] || [ -f "$id_file" ]; then
# Check war manifest
local war_paths=("/usr/share/java/jenkins.war" "/usr/lib/jenkins/jenkins.war" "$JENKINS_HOME/jenkins.war")
for wp in "${war_paths[@]}"; do
if [ -f "$wp" ]; then
unzip -p "$wp" META-INF/MANIFEST.MF 2>/dev/null | grep -i 'Jenkins-Version' | awk -F': ' '{print $2}' | tr -d '\r'
return
fi
done
fi
# Fallback: check via CLI or HTTP
if command -v jenkins-cli &>/dev/null; then
jenkins-cli version 2>/dev/null
return
fi
# Fallback: parse from update center metadata
if [ -f "$JENKINS_HOME/updates/default.json" ]; then
python3 -c "import json,sys; d=json.load(open('$JENKINS_HOME/updates/default.json')); print(d.get('core',{}).get('version',''))" 2>/dev/null
return
fi
echo ""
}
# Check Remoting version from the jar
get_remoting_version() {
local remoting_jar="$JENKINS_HOME/war/WEB-INF/lib/remoting-*.jar"
# Expand glob
for jar in $remoting_jar; do
if [ -f "$jar" ]; then
unzip -p "$jar" META-INF/MANIFEST.MF 2>/dev/null | grep -i 'Implementation-Version\|Bundle-Version' | head -1 | awk -F': ' '{print $2}' | tr -d '\r'
return
fi
done
# Try extracted war directory
local war_paths=("/usr/share/java/jenkins.war" "/usr/lib/jenkins/jenkins.war")
for wp in "${war_paths[@]}"; do
if [ -f "$wp" ]; then
unzip -l "$wp" 2>/dev/null | grep 'remoting-' | awk '{print $NF}' | sed 's/.*remoting-//;s/\.jar//'
return
fi
done
echo ""
}
# Version comparison: returns 0 if $1 >= $2
version_gte() {
[ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" = "$2" ]
}
echo "=== CVE-2026-70426 Check ==="
echo "Jenkins Home: $JENKINS_HOME"
JENKINS_VER=$(get_jenkins_version)
REMOTING_VER=$(get_remoting_version)
echo "Jenkins Version: ${JENKINS_VER:-not detected}"
echo "Remoting Version: ${REMOTING_VER:-not detected}"
# Check for workaround plugin
WORKAROUND_INSTALLED=false
if [ -d "$JENKINS_HOME/plugins" ]; then
if ls "$JENKINS_HOME/plugins/" 2>/dev/null | grep -qi 'SECURITY-3911\|security-3911'; then
WORKAROUND_INSTALLED=true
echo "Workaround plugin: INSTALLED"
fi
fi
if [ -z "$JENKINS_VER" ] && [ -z "$REMOTING_VER" ]; then
echo "Result: UNKNOWN — could not determine Jenkins or Remoting version."
echo "Verify manually: Manage Jenkins > About Jenkins, or check /api/json on the controller."
exit 2
fi
# Fixed versions: Jenkins 2.576+, LTS 2.568.2+
# Remoting fixed: 3385+ (or the exception 3355.3357.v931d3c992987)
if [ -n "$JENKINS_VER" ]; then
# Check if it's an LTS version (x.y.z format)
if echo "$JENKINS_VER" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
# LTS — fixed in 2.568.2
if version_gte "$JENKINS_VER" "2.568.2"; then
RESULT="PATCHED"
else
RESULT="VULNERABLE"
fi
else
# Weekly — fixed in 2.576
if version_gte "$JENKINS_VER" "2.576"; then
RESULT="PATCHED"
else
RESULT="VULNERABLE"
fi
fi
fi
# Override if workaround plugin is installed
if [ "$WORKAROUND_INSTALLED" = true ] && [ "$RESULT" = "VULNERABLE" ]; then
RESULT="PATCHED"
echo "Note: Workaround plugin detected — fallback path is mitigated."
fi
echo "Result: $RESULT"
case "$RESULT" in
PATCHED) exit 0 ;;
VULNERABLE) exit 1 ;;
*) exit 2 ;;
esac