VYPR
Critical severityCISA KEVNVD Advisory· Published Jan 24, 2024· Updated Oct 21, 2025

CVE-2024-23897

CVE-2024-23897

Description

Jenkins 2.441 and earlier, LTS 2.426.2 and earlier does not disable a feature of its CLI command parser that replaces an '@' character followed by a file path in an argument with the file's contents, allowing unauthenticated attackers to read arbitrary files on the Jenkins controller file system.

Affected packages

Versions sourced from the GitHub Security Advisory.

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

Affected products

1
  • Jenkins Project/Jenkinsv5
    Range: 0

Patches

1
554f03782057

[SECURITY-3314]

https://github.com/jenkinsci/jenkinsKevin-CBJan 16, 2024via ghsa
3 files changed · +73 2
  • core/src/main/java/hudson/cli/CLICommand.java+15 1 modified
    @@ -26,6 +26,7 @@
     
     import edu.umd.cs.findbugs.annotations.CheckForNull;
     import edu.umd.cs.findbugs.annotations.NonNull;
    +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     import hudson.AbortException;
     import hudson.Extension;
     import hudson.ExtensionList;
    @@ -51,6 +52,7 @@
     import java.util.logging.Level;
     import java.util.logging.Logger;
     import jenkins.model.Jenkins;
    +import jenkins.util.SystemProperties;
     import org.apache.commons.discovery.ResourceClassIterator;
     import org.apache.commons.discovery.ResourceNameIterator;
     import org.apache.commons.discovery.resource.ClassLoaders;
    @@ -62,6 +64,7 @@
     import org.kohsuke.accmod.restrictions.NoExternalUse;
     import org.kohsuke.args4j.CmdLineException;
     import org.kohsuke.args4j.CmdLineParser;
    +import org.kohsuke.args4j.ParserProperties;
     import org.kohsuke.args4j.spi.OptionHandler;
     import org.springframework.security.access.AccessDeniedException;
     import org.springframework.security.authentication.BadCredentialsException;
    @@ -107,6 +110,16 @@
      */
     @LegacyInstancesAreScopedToHudson
     public abstract class CLICommand implements ExtensionPoint, Cloneable {
    +
    +    /**
    +     * Boolean values to either allow or disallow parsing of @-prefixes.
    +     * If a command line value starts with @, it is interpreted as being a file, loaded,
    +     * and interpreted as if the file content would have been passed to the command line
    +     */
    +    @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts")
    +    @Restricted(NoExternalUse.class)
    +    public static boolean ALLOW_AT_SYNTAX = SystemProperties.getBoolean(CLICommand.class.getName() + ".allowAtSyntax");
    +
         /**
          * Connected to stdout and stderr of the CLI agent that initiated the session.
          * IOW, if you write to these streams, the person who launched the CLI command
    @@ -307,7 +320,8 @@ private void logAndPrintError(Throwable e, String errorMessage, String logMessag
          * @since 1.538
          */
         protected CmdLineParser getCmdLineParser() {
    -        return new CmdLineParser(this);
    +        ParserProperties properties = ParserProperties.defaults().withAtSyntax(ALLOW_AT_SYNTAX);
    +        return new CmdLineParser(this, properties);
         }
     
         /**
    
  • core/src/main/java/hudson/cli/declarative/CLIRegisterer.java+3 1 modified
    @@ -59,6 +59,7 @@
     import org.jvnet.localizer.ResourceBundleHolder;
     import org.kohsuke.args4j.CmdLineException;
     import org.kohsuke.args4j.CmdLineParser;
    +import org.kohsuke.args4j.ParserProperties;
     import org.springframework.security.access.AccessDeniedException;
     import org.springframework.security.authentication.BadCredentialsException;
     import org.springframework.security.core.Authentication;
    @@ -131,7 +132,8 @@ protected CmdLineParser getCmdLineParser() {
                             private CmdLineParser bindMethod(List<MethodBinder> binders) {
     
                                 registerOptionHandlers();
    -                            CmdLineParser parser = new CmdLineParser(null);
    +                            ParserProperties properties = ParserProperties.defaults().withAtSyntax(ALLOW_AT_SYNTAX);
    +                            CmdLineParser parser = new CmdLineParser(null, properties);
     
                                 //  build up the call sequence
                                 Stack<Method> chains = new Stack<>();
    
  • test/src/test/java/jenkins/security/Security3314Test.java+55 0 added
    @@ -0,0 +1,55 @@
    +package jenkins.security;
    +
    +import static org.hamcrest.MatcherAssert.assertThat;
    +import static org.hamcrest.Matchers.containsString;
    +import static org.hamcrest.Matchers.not;
    +
    +import hudson.cli.CLICommandInvoker;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.util.Arrays;
    +import java.util.List;
    +import jenkins.model.Jenkins;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.junit.runner.RunWith;
    +import org.junit.runners.Parameterized;
    +import org.jvnet.hudson.test.JenkinsRule;
    +
    +@RunWith(Parameterized.class)
    +public class Security3314Test {
    +    private String commandName;
    +
    +    @Rule
    +    public final JenkinsRule j = new JenkinsRule();
    +
    +    /**
    +     * connect-node to test the CLICommand behavior
    +     * disable-job to test the CLIRegisterer behavior (@CLIMethod)
    +     */
    +    @Parameterized.Parameters
    +    public static List<String> commands() {
    +        return Arrays.asList("connect-node", "disable-job");
    +    }
    +
    +    public Security3314Test(String commandName) {
    +        this.commandName = commandName;
    +    }
    +
    +    @Test
    +    public void commandShouldNotParseAt() throws Exception {
    +        CLICommandInvoker command = new CLICommandInvoker(j, commandName);
    +
    +        Path tempPath = Files.createTempFile("tempFile", ".txt");
    +        tempPath.toFile().deleteOnExit();
    +        String content = "AtGotParsed";
    +        Files.write(tempPath, content.getBytes());
    +
    +        final CLICommandInvoker.Result result = command
    +                .authorizedTo(Jenkins.READ)
    +                .invokeWithArgs("@" + tempPath);
    +
    +        assertThat(result.stderr(), containsString("@" + tempPath));
    +        assertThat(result.stderr(), not(containsString("AtGotParsed")));
    +    }
    +}
    

Vulnerability mechanics

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

References

12

News mentions

0

No linked articles in our index yet.