VYPR
High severityNVD Advisory· Published Jan 24, 2024· Updated Jun 20, 2025

CVE-2024-23898

CVE-2024-23898

Description

Jenkins 2.217 through 2.441 (both inclusive), LTS 2.222.1 through 2.426.2 (both inclusive) does not perform origin validation of requests made through the CLI WebSocket endpoint, resulting in a cross-site WebSocket hijacking (CSWSH) vulnerability, allowing attackers to execute CLI commands on the Jenkins controller.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Jenkins fails to validate origin of WebSocket requests, enabling cross-site WebSocket hijacking and arbitrary CLI command execution.

Vulnerability

Overview

CVE-2024-23898 is a cross-site WebSocket hijacking (CSWSH) vulnerability in Jenkins core versions 2.217 through 2.441 and LTS 2.222.1 through 2.426.2. The Jenkins CLI WebSocket endpoint does not validate the origin of incoming requests, allowing an attacker to perform a cross-site WebSocket hijacking attack [1][2][4].

Exploitation

The attacker can craft a malicious web page that, when visited by an authenticated Jenkins user, initiates a WebSocket connection to the Jenkins controller from the attacker's site. Because Jenkins fails to check the Origin header, the connection is accepted, and the attacker can then issue CLI commands on behalf of the victim [2].

Impact

A successful attack allows the attacker to execute arbitrary CLI commands on the Jenkins controller with the permissions of the targeted user. This could lead to information disclosure, privilege escalation, or full control of the Jenkins instance, depending on the user's role [2][4].

Mitigation

The vulnerability has been fixed in Jenkins 2.442 and LTS 2.426.3. Users should upgrade immediately. No workaround is available for affected versions [1][3].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.jenkins-ci.main:jenkins-coreMaven
>= 2.217, < 2.426.32.426.3
org.jenkins-ci.main:jenkins-coreMaven
>= 2.427, < 2.4422.442

Affected products

4

Patches

1
de450967f383

[SECURITY-3315]

https://github.com/jenkinsci/jenkinsDaniel BeckJan 16, 2024via ghsa
2 files changed · +82 1
  • core/src/main/java/hudson/cli/CLIAction.java+20 1 modified
    @@ -49,8 +49,10 @@
     import javax.servlet.http.HttpServletResponse;
     import jenkins.model.Jenkins;
     import jenkins.util.FullDuplexHttpService;
    +import jenkins.util.SystemProperties;
     import jenkins.websocket.WebSocketSession;
     import jenkins.websocket.WebSockets;
    +import org.apache.commons.lang.StringUtils;
     import org.jenkinsci.Symbol;
     import org.kohsuke.accmod.Restricted;
     import org.kohsuke.accmod.restrictions.NoExternalUse;
    @@ -73,6 +75,12 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {
     
         private static final Logger LOGGER = Logger.getLogger(CLIAction.class.getName());
     
    +    /**
    +     * Boolean values map to allowing/disallowing WS CLI endpoint always, {@code null} is the default of doing an {@code Origin} check.
    +     * {@code true} is only advisable if anonymous users have no permissions, and Jenkins sends SameSite=Lax cookies (or browsers use that as the implicit default).
    +     */
    +    /* package-private for testing */ static /* non-final for Script Console */ Boolean ALLOW_WEBSOCKET = SystemProperties.optBoolean(CLIAction.class.getName() + ".ALLOW_WEBSOCKET");
    +
         private final transient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();
     
         @Override
    @@ -114,10 +122,21 @@ public boolean isWebSocketSupported() {
         /**
          * WebSocket endpoint.
          */
    -    public HttpResponse doWs() {
    +    public HttpResponse doWs(StaplerRequest req) {
             if (!WebSockets.isSupported()) {
                 return HttpResponses.notFound();
             }
    +        if (ALLOW_WEBSOCKET == null) {
    +            final String actualOrigin = req.getHeader("Origin");
    +            final String expectedOrigin = StringUtils.removeEnd(StringUtils.removeEnd(Jenkins.get().getRootUrlFromRequest(), "/"), req.getContextPath());
    +
    +            if (actualOrigin == null || !actualOrigin.equals(expectedOrigin)) {
    +                LOGGER.log(Level.FINE, () -> "Rejecting origin: " + actualOrigin + "; expected was from request: " + expectedOrigin);
    +                return HttpResponses.forbidden();
    +            }
    +        } else if (!ALLOW_WEBSOCKET) {
    +            return HttpResponses.forbidden();
    +        }
             Authentication authentication = Jenkins.getAuthentication2();
             return WebSockets.upgrade(new WebSocketSession() {
                 ServerSideImpl connection;
    
  • test/src/test/java/hudson/cli/Security3315Test.java+62 0 added
    @@ -0,0 +1,62 @@
    +package hudson.cli;
    +
    +import static org.hamcrest.CoreMatchers.is;
    +import static org.hamcrest.MatcherAssert.assertThat;
    +
    +import java.io.IOException;
    +import java.net.URL;
    +import java.util.Arrays;
    +import java.util.List;
    +import org.htmlunit.HttpMethod;
    +import org.htmlunit.Page;
    +import org.htmlunit.WebRequest;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.junit.runner.RunWith;
    +import org.junit.runners.Parameterized;
    +import org.jvnet.hudson.test.FlagRule;
    +import org.jvnet.hudson.test.JenkinsRule;
    +
    +@RunWith(Parameterized.class)
    +public class Security3315Test {
    +    @Rule
    +    public JenkinsRule j = new JenkinsRule();
    +
    +    @Rule
    +    public FlagRule<Boolean> escapeHatch;
    +
    +    private final Boolean allowWs;
    +
    +    @Parameterized.Parameters
    +    public static List<String> escapeHatchValues() {
    +        return Arrays.asList(null, "true", "false");
    +    }
    +
    +    public Security3315Test(String allowWs) {
    +        this.allowWs = allowWs == null ? null : Boolean.valueOf(allowWs);
    +        this.escapeHatch = new FlagRule<>(() -> CLIAction.ALLOW_WEBSOCKET, v -> { CLIAction.ALLOW_WEBSOCKET = v; }, this.allowWs);
    +    }
    +
    +    @Test
    +    public void test() throws IOException {
    +        try (JenkinsRule.WebClient wc = j.createWebClient().withThrowExceptionOnFailingStatusCode(false)) {
    +            // HTTP 400 is WebSocket "success" (HTMLUnit doesn't support it)
    +            final URL jenkinsUrl = j.getURL();
    +            WebRequest request = new WebRequest(new URL(jenkinsUrl.toString() + "cli/ws"), HttpMethod.GET);
    +            Page page = wc.getPage(request);
    +            assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // no Origin header
    +
    +            request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://example.org:" + jenkinsUrl.getPort());
    +            page = wc.getPage(request);
    +            assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // Wrong Origin host
    +
    +            request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://" + jenkinsUrl.getHost());
    +            page = wc.getPage(request);
    +            assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.TRUE ? 400 : 403)); // Wrong Origin port
    +
    +            request.setAdditionalHeader("Origin", jenkinsUrl.getProtocol() + "://" + jenkinsUrl.getHost() + ":" + jenkinsUrl.getPort());
    +            page = wc.getPage(request);
    +            assertThat(page.getWebResponse().getStatusCode(), is(allowWs == Boolean.FALSE ? 403 : 400)); // Reject correct Origin if ALLOW_WS is explicitly false
    +        }
    +    }
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

1