CVE-2024-23897 — Jenkins CLI arbitrary file read (and the RCE chain)
An unauthenticated request to the Jenkins built-in CLI can read arbitrary files off the controller — first three lines for anonymous, the whole file as soon as any user has overall/Read. It was KEV-listed within days because the chain from "read three lines" to "remote code execution on the controller" is short, public, and almost entirely automated.
TL;DR
- Unauthenticated arbitrary file read in the Jenkins built-in CLI; root cause is args4j's
@/path/to/fileargument expansion, which Jenkins inherited rather than wrote. - CVSS 9.8 (Critical) on the file-read primitive alone. KEV-listed by CISA within days of the 2024-01-24 advisory.
- Anonymous users get the first three lines of any readable file; any user with overall/Read gets the full file.
- Chains trivially to RCE by stealing
$JENKINS_HOME/secrets/master.keyandsecrets/hudson.util.Secret, then decrypting stored credentials (SSH keys, cloud tokens, API keys). - Fixed in Jenkins LTS 2.426.3 and weekly 2.442 — both shipped on the disclosure day.
- If you can't patch immediately, set
-Dhudson.cli.CLICommand.allowAtSyntax=falseon the JVM as a documented workaround.
At a glance
| CVE ID | CVE-2024-23897 |
|---|---|
| Severity | Critical (CVSS 9.8) · KEV-listed |
| CVSS 3.1 score | 9.8 |
| CVSS vector | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) |
| CISA KEV listed | Yes — added within days of disclosure |
| Published | 2024-01-24 (Jenkins Security Advisory) |
| Primary impact | Arbitrary file read on the Jenkins controller; chains to RCE |
| Fixed in | Jenkins LTS 2.426.3 · Jenkins weekly 2.442 |
What goes wrong in the CLI parser
Jenkins ships a built-in command-line interface that's reachable over HTTP and SSH on every install by default. Argument parsing for that CLI is delegated to args4j, a third-party library Jenkins has used for years.
args4j has a long-standing convenience feature: any argument of
the form @/path/to/file gets transparently expanded
to the contents of that file before the command runs.
It's the same idea as curl @file or
javac @argfile — a developer convenience for
passing long argument lists.
Jenkins never disabled that behaviour for the CLI endpoint. So
when an unauthenticated request comes in carrying an argument
like @/etc/passwd, the parser dutifully opens the
file on the controller, reads it, and feeds the contents in as
the argument value. Jenkins then echoes the parser's error
message back to the attacker — and the error message contains
what it just read.
The root cause matters: this is a Jenkins consumer of a third-party library doing exactly what the library was designed to do. The bug is that nobody, on either side, treated the CLI endpoint as an untrusted boundary that shouldn't be expanding server-side file paths.
The unauthenticated → authenticated → RCE chain
At first glance "unauthenticated, three lines" sounds survivable. It isn't, for three reasons that compose.
One: three lines is plenty for reconnaissance. An
anonymous read of /etc/passwd tells you the
controller's OS family and any service accounts on it. Three
lines of $JENKINS_HOME/config.xml tells you the
authentication realm, the authorisation strategy, and whether
anonymous overall/Read is enabled.
Two: many real Jenkins installs grant overall/Read to authenticated users by default — sometimes even to anonymous, depending on how the security realm was configured in the early-2010s setup wizard. The moment any user account is reachable, the read becomes unbounded: full files, no three-line cap.
Three: full file read of a Jenkins controller is
effectively full credential disclosure. Two files in
$JENKINS_HOME/secrets/ matter:
secrets/master.key— the controller's master encryption key.secrets/hudson.util.Secret— encrypted with the master key; decrypts to the AES key that protects every credential stored in Jenkins.
Once an attacker has both, every credential the Jenkins admin ever pasted into the credentials store — deploy SSH keys, AWS access keys, GitHub tokens, Docker registry passwords, signing certificates — comes out in plaintext. From there the chain to RCE is whichever credential is most useful: an SSH key into a build agent, an AWS key into the deploy pipeline, or a credential that lets you create a new freestyle job that runs shell commands as the Jenkins user.
Public PoCs landed within 24 hours of the advisory and walked
exactly this path: read /etc/passwd, read
master.key, read hudson.util.Secret,
decrypt the credential XML, pivot.
Affected versions and fix paths
| Track | Vulnerable | Fixed in |
|---|---|---|
| Jenkins LTS | ≤ 2.426.2 | 2.426.3 |
| Jenkins weekly | ≤ 2.441 | 2.442 |
jenkins/jenkins:lts (Docker) | Tags built before 2024-01-24 | Tags published 2024-01-24 and later |
jenkins/jenkins:lts-jdk17 (Docker) | Tags built before 2024-01-24 | Tags published 2024-01-24 and later |
The same Docker image tags get re-published as the underlying
LTS line ships fixes, so a stale :lts on a homelab
Jenkins is still vulnerable until the image is re-pulled and
the container restarted.
Quick scan check
Three angles. The first works without any access, the second needs the CLI jar, the third needs shell access on the controller.
# 1. Anonymous version disclosure on the login page
curl -s http://your-jenkins/login | grep -oE 'Jenkins [0-9.]+'
# 2. CLI is on by default; the version shows in stderr
java -jar jenkins-cli.jar -s http://your-jenkins help 2>&1 | head
# 3. On the controller itself
cat "$JENKINS_HOME/jenkins.install.UpgradeWizard.state"
cat "$JENKINS_HOME/config.xml" | grep -i version
If the version is at or below 2.426.2 on the LTS line, or at or below 2.441 on the weekly line, the install is vulnerable.
Mitigations beyond patching
- Patch first. Jenkins LTS 2.426.3 and weekly 2.442 are the only complete fixes.
- Disable the @-syntax if you can't patch immediately by adding
-Dhudson.cli.CLICommand.allowAtSyntax=falseto the JVM args. This is the upstream-documented workaround and removes the file-read primitive entirely. - Put Jenkins behind a reverse proxy with auth in front — at minimum HTTP basic auth or an OAuth proxy. The CLI endpoint then can't be reached unauthenticated even if a future bug ships.
- Never expose Jenkins on the public internet. Reverse-proxy-with-auth is a stopgap, not a posture; a CI controller belongs on a VPN or behind Cloudflare Access.
- Rotate every stored credential if a vulnerable Jenkins was reachable from anything you don't fully control — including the LAN. Assume the credential store leaked; rotate the SSH keys, the cloud tokens, the registry passwords, all of it.
For the broader framing on exposed-by-default services, see exposed admin surfaces as a homelab attack vector and before you expose a service to the web.
Why this matters more in homelabs than enterprises
Enterprise Jenkins almost always lives behind SSO, a corporate WAF, and a network team that has opinions about who reaches port 8080. The CVE still mattered there — patching CI is its own ordeal — but the attack surface was bounded.
Homelab Jenkins is different. It's typically reachable from any device on the LAN, and "any device on the LAN" includes the IoT cameras, the smart bulbs, the one Windows laptop your kid uses for school, and any guest who joined the wifi. There's no SSO, often no reverse proxy, and frequently a port-forward to the public internet because "I want to trigger builds from my phone." The credential store almost always contains an SSH key that gets the attacker to the rest of the fleet — see the patching gap on self-hosted services for why this pattern is so common.
What Noxen does about this
Noxen flags Jenkins web UIs as exposed admin surfaces during a
port scan — the Jenkins login page fingerprints unambiguously,
and the finding includes the version string from the page. On
hosts where the Jenkins controller is reachable over SSH, the
package-manifest probe picks up the jenkins
package version (apt or rpm), and the matcher correlates that
against CVE-2024-23897 in the signed daily feed. The finding
ships with remediation guidance pointing at the LTS 2.426.3 /
weekly 2.442 fix lines and the allowAtSyntax
workaround.
For the triage framing — when a Jenkins finding is "patch tonight" vs. "patch this week" — see triaging CVE findings: a severity guide.
Authoritative sources
- NVD entry for CVE-2024-23897
- cve.org record
- Jenkins Security Advisory 2024-01-24
- CISA KEV catalogue (search for CVE-2024-23897)
See what Noxen does about CVEs like this → More on CVE management →