This is a visitor badge that can turn into the master key for your build factory
CVE-2026-53435 is an unsafe deserialization flaw in Jenkins core affecting weekly releases through 2.567 and LTS through 2.555.2, fixed in 2.568 and 2.555.3. An attacker who can reach the relevant config.xml workflow with the required Jenkins permissions can make Jenkins deserialize unexpected core or plugin types, then reach those objects through Stapler HTTP routing; the documented outcomes include arbitrary file read from the controller, user impersonation, and requests sent as other users up to Script Console access and code execution.
The vendor's HIGH 8.8 is fair if you score only the entry conditions, but too low for real enterprise blast radius. Jenkins is a CI/CD control plane, so successful exploitation is not just 'one app server got popped'—it is a realistic *supply-chain pivot* into signing material, deployment credentials, build secrets, artifacts, and downstream estate.
4 steps from start to impact.
Land a Jenkins foothold
Overall/Read, or another permission set that reaches a POST config.xml path. In practice this usually means stolen SSO credentials, a compromised developer account, an insider, or an already-established internal foothold using standard HTTP tooling like curl, Burp, or a custom requests script.- Valid Jenkins account or equivalent permissions
- Target is running Jenkins
<=2.567or LTS<=2.555.2 - Relevant HTTP endpoints are reachable
- Not anonymous; this is *not* an internet-spray unauthenticated bug
- MFA, SSO, IP allowlists, and sane RBAC cut off a lot of opportunistic abuse
- Many Jenkins deployments do not grant broad low-privilege users configuration rights
Plant a routable deserialized object
config.xml submission, the attacker coerces Jenkins into deserializing an unexpected but Jenkins-resident type that survives the class filter. Public research from AmesianX shows one concrete path using hudson.Plugin$DummyImpl inside a DescribableList<ViewProperty> so the object persists and becomes reachable through Stapler routing.- A writable
config.xmlpath or other advisory-described authenticated path - Knowledge of a Jenkins/core or plugin type that fits the sink
- Generic Java gadget chains do not automatically work because Jenkins filters deserialization to core/plugin-defined classes
- The exact sink matters; many naive payloads fail
- Plugin mix and object graph details can change which gadgets are practical
POST .../config.xml events, suspicious object persistence in view/job configs, and odd paths in Jenkins access logs after a config update.Abuse Stapler-exposed behavior
- Deserialized object remains reachable after save/load
- Controller HTTP routing exposes useful methods or resources
- Some deployments have fewer useful plugins or disabled admin features
- The public PoC proves file read; full RCE chains may require selecting a better target path or privileged destination
Pivot from controller compromise to supply chain
- Jenkins holds meaningful secrets or deployment authority
- Controller connects to SCM, registries, clouds, package repos, or production environments
- Blast radius is smaller on isolated lab instances with no secrets or prod connectivity
- Well-segmented agents, short-lived credentials, and secret managers reduce follow-on value
The supporting signals.
| In-the-wild status | No evidence of active exploitation in the sources reviewed; CISA KEV: No, and the Jenkins advisory does not claim live attacks. |
|---|---|
| Public exploit status | Yes. A public GitHub PoC by AmesianX demonstrates authenticated arbitrary file read on vulnerable 2.555.2 and failure on patched 2.555.3; RCE chain details were intentionally withheld. |
| EPSS | 0.00368 from the provided intel and VulDB, with VulDB labeling activity as *very low*. That is a threat-likelihood signal, not an impact signal. |
| KEV | Not listed in the CISA Known Exploited Vulnerabilities catalog. |
| CVSS vector reality | AV:N/AC:L/PR:L/UI:N means the *technical* chain is easy once you already have the required Jenkins foothold. The decisive caveat is PR:L: this is post-auth, not anonymous edge smash-and-grab. |
| Affected versions | Jenkins weekly <=2.567; Jenkins LTS <=2.555.2. |
| Fixed versions | Upgrade to weekly 2.568 or LTS 2.555.3. |
| Exposure picture | Shodan's Jenkins search shows broad public exposure potential, with roughly 46k Jenkins product banners and about 1k pages titled Dashboard [Jenkins] in the surfaced snapshot. That overstates exploitable population because this CVE still needs auth and the right Jenkins permissions. |
| Disclosure timeline | Disclosed 2026-06-10; NVD published 2026-06-10 and shows last modification 2026-06-11. |
| Reporter | Reported to Jenkins as SECURITY-3707 by dqh1 through the Jenkins Bug Bounty Program. |
noisgate verdict.
This lands in CRITICAL because the single decisive factor is the role multiplier — Jenkins is a CI/CD controller, so successful exploitation is a supply-chain pivot, not a single-host incident. The authenticated starting point absolutely adds friction, but it does not break the critical floor when the documented chain can end in controller RCE, artifact tampering, secret theft, and downstream fleet compromise.
Why this verdict
- Downward pressure — authenticated starting point: Step 1 requires
Overall/Readplus a real account or other permission set reachingconfig.xml. That implies credential theft, insider access, or an earlier foothold; MFA, SSO, and RBAC should stop a large slice of opportunistic internet noise. - Downward pressure — exposed population narrows fast: Raw Shodan Jenkins counts are not the exploitable population. You need a vulnerable version, reachable login surface, a valid identity, and a permission model loose enough to hit a workable path; each prerequisite compounds downward pressure on mass exploitation.
- Upward pressure — exploitability is no longer theoretical: A public PoC exists and demonstrates authenticated arbitrary file read with patched-vs-vulnerable differential validation. That collapses a lot of uncertainty around whether the advisory was merely academic.
- Role multiplier: On a low-value role like a dev sandbox, the chain may end at one controller and a few test credentials. On a typical role like a shared line-of-business build server, it plausibly ends in host compromise plus theft of SCM, artifact, and deployment credentials. On the high-value canonical role—production CI/CD controller—the documented chain succeeds as a supply-chain pivot, with blast radius from *host* to *fleet* to *downstream software consumers*. Jenkins is itself a CI/CD platform, so this high-value role is common enough to set a CRITICAL floor rather than being a corner case.
Why not higher?
It is not a perfect 10 because the chain is not unauthenticated, not KEV-listed, and not backed by confirmed in-the-wild exploitation. Real attackers still need a valid Jenkins foothold and a permission model that lets them reach a practical deserialization path.
Why not lower?
You cannot responsibly file this under ordinary authenticated app risk because the impact is not bounded to one tenant or one workflow. Once you grant that the advisory's impersonation and Script Console outcomes are plausible on a Jenkins controller, the asset's native role makes this a CI/CD and supply-chain incident class, which floors severity at CRITICAL.
What to do — in priority order.
- Restrict Jenkins exposure — Move controllers behind VPN, SSO-aware reverse proxy, or internal-only access where possible, and remove direct internet reachability for admin and user portals. For a CRITICAL verdict, deploy this compensating control within 3 days to shrink the reachable population while patch scheduling catches up.
- Tighten
Overall/Readand config permissions — Audit matrix/role permissions and strip broad groups ofOverall/Read,View/Configure,Item/Configure, andAgent/Configureunless there is a hard business need. For a CRITICAL verdict, complete the first-pass permission reduction within 3 days because this CVE's exploitability is permission-gated. - Kill stale local users and rotate exposed secrets — Disable dormant Jenkins-local accounts, review SSO group mappings, and rotate any controller-resident secrets that would matter if file read already occurred: API tokens, SCM PATs, cloud keys, signing creds, kubeconfigs, and deployment secrets. Start within 3 days on internet-facing or high-value controllers first.
- Instrument config and admin-path logging — Alert on
POSTrequests toconfig.xml, creation or modification of views/jobs/nodes by low-privilege identities, and any Script Console access. Stand this up within 3 days so you have coverage for attempted exploitation before every node is remediated. - Constrain controller blast radius — Reduce the controller's standing privileges to SCM, cloud, registries, and agents; prefer short-lived credentials from a secrets manager; and verify artifact signing and deployment approvals. Begin within 3 days on production CI/CD because this control limits the supply-chain damage even if one controller is hit.
- A generic WAF does not reliably solve this because the exploit lives in authenticated
config.xmlworkflows and legitimate-looking Stapler routes, not a single obvious signatureable payload. - Relying on low EPSS alone is the wrong call; EPSS speaks to observed exploitation probability, not the blast radius of a CI/CD controller compromise.
- Disabling anonymous access only is insufficient because the advisory explicitly allows exploitation with a real user account and
Overall/Read. - Plugin pruning by itself is incomplete; the flaw is in Jenkins core deserialization and routing behavior, even though plugin-defined types can expand gadget options.
Crowdsourced verification payload.
Run this on the Jenkins controller or from an auditor workstation that can reach the Jenkins HTTP endpoint. Example invocations: python3 verify_cve_2026_53435.py --url https://jenkins.example.com/ or python3 verify_cve_2026_53435.py --war /usr/share/java/jenkins.war; no admin rights are needed for remote URL checks, but local file checks need read access to the WAR/package path.
#!/usr/bin/env python3
# verify_cve_2026_53435.py
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN
import argparse
import os
import re
import sys
import zipfile
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
AFFECTED_WEEKLY_MAX = (2, 567, 0)
AFFECTED_LTS_MAX = (2, 555, 2)
FIXED_WEEKLY = '2.568'
FIXED_LTS = '2.555.3'
def parse_version(v):
if not v:
return None
m = re.search(r'(\d+)\.(\d+)(?:\.(\d+))?', v.strip())
if not m:
return None
return tuple(int(x) if x is not None else 0 for x in m.groups(default='0'))
def classify(vstr):
vp = parse_version(vstr)
if not vp:
return ('UNKNOWN', f'Could not parse version: {vstr!r}')
if len(vp) == 2:
vp = (vp[0], vp[1], 0)
if vp[:2] == (2, 555) and vp <= AFFECTED_LTS_MAX:
return ('VULNERABLE', f'Jenkins LTS {vstr} <= {FIXED_LTS} fixed threshold')
if vp <= AFFECTED_WEEKLY_MAX:
return ('VULNERABLE', f'Jenkins weekly {vstr} <= {FIXED_WEEKLY} fixed threshold')
if vp >= parse_version(FIXED_LTS) or vp >= parse_version(FIXED_WEEKLY):
return ('PATCHED', f'Jenkins version {vstr} is newer than affected ranges')
return ('UNKNOWN', f'Version {vstr} did not cleanly map to weekly/LTS logic')
def version_from_url(base_url, timeout=10):
base_url = base_url.rstrip('/') + '/login'
req = Request(base_url, headers={'User-Agent': 'noisgate-cve-check/1.0'})
with urlopen(req, timeout=timeout) as resp:
headers = resp.headers
candidates = [
headers.get('X-Jenkins'),
headers.get('X-Hudson'),
]
for c in candidates:
if c:
return c.strip(), f'HTTP header from {base_url}'
body = resp.read(4096).decode('utf-8', errors='ignore')
m = re.search(r'Jenkins\s+ver\.\s*([0-9][0-9.]+)', body, re.I)
if m:
return m.group(1), f'HTML body from {base_url}'
return None, 'No Jenkins version header/body marker found'
def version_from_war(path):
with zipfile.ZipFile(path, 'r') as zf:
for candidate in ['META-INF/MANIFEST.MF', 'WEB-INF/classes/jenkins/model/Jenkins.class']:
if candidate in zf.namelist():
break
manifest = zf.read('META-INF/MANIFEST.MF').decode('utf-8', errors='ignore')
for line in manifest.splitlines():
if line.startswith('Jenkins-Version:'):
return line.split(':', 1)[1].strip(), f'MANIFEST in {path}'
if line.startswith('Implementation-Version:'):
return line.split(':', 1)[1].strip(), f'MANIFEST in {path}'
return None, f'No version found in {path}'
def main():
ap = argparse.ArgumentParser(description='Check Jenkins for CVE-2026-53435 by version')
ap.add_argument('--url', help='Base Jenkins URL, e.g. https://jenkins.example.com/')
ap.add_argument('--war', help='Path to jenkins.war')
args = ap.parse_args()
if not args.url and not args.war:
print('UNKNOWN - provide --url or --war')
sys.exit(2)
try:
if args.url:
version, source = version_from_url(args.url)
else:
if not os.path.exists(args.war):
print(f'UNKNOWN - WAR not found: {args.war}')
sys.exit(2)
version, source = version_from_war(args.war)
except (HTTPError, URLError, TimeoutError, OSError, zipfile.BadZipFile) as e:
print(f'UNKNOWN - lookup failed: {e}')
sys.exit(2)
except Exception as e:
print(f'UNKNOWN - unexpected error: {e}')
sys.exit(2)
if not version:
print(f'UNKNOWN - no version detected ({source})')
sys.exit(2)
status, reason = classify(version)
print(f'{status} - detected Jenkins {version} via {source}; {reason}')
if status == 'PATCHED':
sys.exit(0)
if status == 'VULNERABLE':
sys.exit(1)
sys.exit(2)
if __name__ == '__main__':
main()
If you remember one thing.
Overall/Read and config.xml-relevant permissions, disable stale accounts, and turn on logging for config.xml writes and Script Console access—then complete upgrades to 2.568 or 2.555.3 within the noisgate remediation SLA of 90 days, with high-value controllers done first rather than last.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.