CVE feed → CVE-2024-23897

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

At a glance

CVE IDCVE-2024-23897
SeverityCritical (CVSS 9.8) · KEV-listed
CVSS 3.1 score9.8
CVSS vectorAV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWECWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
CISA KEV listedYes — added within days of disclosure
Published2024-01-24 (Jenkins Security Advisory)
Primary impactArbitrary file read on the Jenkins controller; chains to RCE
Fixed inJenkins 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:

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.22.426.3
Jenkins weekly≤ 2.4412.442
jenkins/jenkins:lts (Docker)Tags built before 2024-01-24Tags published 2024-01-24 and later
jenkins/jenkins:lts-jdk17 (Docker)Tags built before 2024-01-24Tags 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

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

See what Noxen does about CVEs like this →   More on CVE management →