VYPR
High severityNVD Advisory· Published Sep 20, 2023· Updated May 2, 2025

CVE-2023-43496

CVE-2023-43496

Description

Jenkins 2.423 and earlier, LTS 2.414.1 and earlier creates a temporary file in the system temporary directory with the default permissions for newly created files when installing a plugin from a URL, potentially allowing attackers with access to the system temporary directory to replace the file before it is installed in Jenkins, potentially resulting in arbitrary code execution.

AI Insight

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

Jenkins creates a temporary plugin file with insecure default permissions, enabling local attackers to replace it and achieve arbitrary code execution.

Vulnerability

Jenkins 2.423 and earlier, and LTS 2.414.1 and earlier, creates a temporary file in the system temporary directory with default permissions when installing a plugin from a URL [1][2]. This file is used to stage the plugin before installation, but because the permissions are not restricted, any user or process with access to the temporary directory can read or modify it [2].

Exploitation

An attacker with local access to the system temporary directory can replace the temporary plugin file with a malicious one before Jenkins installs it [2]. No authentication is required beyond the ability to write to the temporary directory. This is a classic time-of-check time-of-use (TOCTOU) race condition [4].

Impact

Successful exploitation allows arbitrary code execution in the context of the Jenkins process [1][2]. This could lead to full compromise of the Jenkins server and any connected systems.

Mitigation

The vulnerability is fixed in Jenkins 2.424 and LTS 2.414.2 [2]. The fix ensures the temporary file is created with restrictive permissions (owner-only read/write/execute) [4]. No workaround is available; users should upgrade immediately.

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.50, < 2.414.22.414.2
org.jenkins-ci.main:jenkins-coreMaven
>= 2.415, < 2.4242.424

Affected products

3

Patches

1
df7c4ccda897

[SECURITY-3072]

https://github.com/jenkinsci/jenkinsKevin-CBSep 6, 2023via ghsa
3 files changed · +128 27
  • core/src/main/java/hudson/PluginManager.java+2 19 modified
    @@ -28,8 +28,6 @@
     import static hudson.init.InitMilestone.PLUGINS_LISTED;
     import static hudson.init.InitMilestone.PLUGINS_PREPARED;
     import static hudson.init.InitMilestone.PLUGINS_STARTED;
    -import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
    -import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
     import static java.util.logging.Level.FINE;
     import static java.util.logging.Level.INFO;
     import static java.util.logging.Level.WARNING;
    @@ -83,18 +81,15 @@
     import java.net.URLConnection;
     import java.net.http.HttpClient;
     import java.net.http.HttpRequest;
    -import java.nio.file.FileSystems;
     import java.nio.file.Files;
     import java.nio.file.InvalidPathException;
     import java.nio.file.Paths;
     import java.nio.file.attribute.FileTime;
     import java.security.CodeSource;
     import java.time.Duration;
     import java.util.ArrayList;
    -import java.util.Arrays;
     import java.util.Collection;
     import java.util.Collections;
    -import java.util.EnumSet;
     import java.util.Enumeration;
     import java.util.HashMap;
     import java.util.HashSet;
    @@ -1883,16 +1878,6 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl
                     copier = new FileUploadPluginCopier(fileItem);
                 }
     
    -            if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
    -                Arrays.stream(Objects.requireNonNull(tmpDir.listFiles())).forEach((file -> {
    -                    try {
    -                        Files.setPosixFilePermissions(file.toPath(), EnumSet.of(OWNER_READ, OWNER_WRITE));
    -                    } catch (IOException e) {
    -                        throw new RuntimeException(e);
    -                    }
    -                }));
    -            }
    -
                 if ("".equals(fileName)) {
                     return new HttpRedirect("advanced");
                 }
    @@ -1902,7 +1887,8 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl
                 }
     
                 // first copy into a temporary file name
    -            File t = File.createTempFile("uploaded", ".jpi");
    +            File t = File.createTempFile("uploaded", ".jpi", tmpDir);
    +            tmpDir.deleteOnExit();
                 t.deleteOnExit();
                 // TODO Remove this workaround after FILEUPLOAD-293 is resolved.
                 Files.delete(Util.fileToPath(t));
    @@ -1913,9 +1899,6 @@ public HttpResponse doUploadPlugin(StaplerRequest req) throws IOException, Servl
                     throw new ServletException(e);
                 }
                 copier.cleanup();
    -            if (!tmpDir.delete()) {
    -                System.err.println("Failed to delete temporary directory: " + tmpDir);
    -            }
     
                 final String baseName = identifyPluginShortName(t);
     
    
  • test/src/test/java/hudson/PluginManagerSecurity3072Test.java+107 0 added
    @@ -0,0 +1,107 @@
    +package hudson;
    +
    +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
    +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
    +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
    +import static org.awaitility.Awaitility.await;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertTrue;
    +import static org.junit.Assume.assumeFalse;
    +
    +import hudson.model.RootAction;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.LinkOption;
    +import java.nio.file.attribute.PosixFilePermission;
    +import java.util.Arrays;
    +import java.util.Comparator;
    +import java.util.EnumSet;
    +import java.util.HashSet;
    +import java.util.Objects;
    +import java.util.Optional;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import javax.servlet.ServletException;
    +import jenkins.model.Jenkins;
    +import org.htmlunit.html.HtmlForm;
    +import org.htmlunit.html.HtmlPage;
    +import org.junit.Rule;
    +import org.junit.Test;
    +import org.junit.rules.TemporaryFolder;
    +import org.jvnet.hudson.test.Issue;
    +import org.jvnet.hudson.test.JenkinsRule;
    +import org.jvnet.hudson.test.TestExtension;
    +import org.kohsuke.stapler.StaplerRequest;
    +import org.kohsuke.stapler.StaplerResponse;
    +
    +public class PluginManagerSecurity3072Test {
    +
    +    @Rule
    +    public JenkinsRule r = PluginManagerUtil.newJenkinsRule();
    +
    +    @Rule
    +    public TemporaryFolder tmp = new TemporaryFolder();
    +
    +    @Test
    +    @Issue("SECURITY-3072")
    +    public void verifyUploadedPluginFromURLPermission() throws Exception {
    +        assumeFalse(Functions.isWindows());
    +
    +        HtmlPage page = r.createWebClient().goTo("pluginManager/advanced");
    +        HtmlForm f = page.getFormByName("uploadPlugin");
    +        f.getInputByName("pluginUrl").setValue(Jenkins.get().getRootUrl() + "pluginManagerGetPlugin/htmlpublisher.jpi");
    +        r.submit(f);
    +
    +        File filesRef = Files.createTempFile("tmp", ".tmp").toFile();
    +        File filesTmpDir = filesRef.getParentFile();
    +        filesRef.deleteOnExit();
    +
    +        final Set<PosixFilePermission>[] filesPermission = new Set[]{new HashSet<>()};
    +        await().pollInterval(250, TimeUnit.MILLISECONDS)
    +                .atMost(10, TimeUnit.SECONDS)
    +                .until(() -> {
    +                    Optional<File> lastUploadedPluginDir = Arrays.stream(Objects.requireNonNull(
    +                                filesTmpDir.listFiles((file, fileName) ->
    +                                    fileName.startsWith("uploadDir")))).
    +                                    max(Comparator.comparingLong(File::lastModified));
    +                    if (lastUploadedPluginDir.isPresent()) {
    +                        filesPermission[0] = Files.getPosixFilePermissions(lastUploadedPluginDir.get().toPath(), LinkOption.NOFOLLOW_LINKS);
    +                        Optional<File> pluginFile = Arrays.stream(Objects.requireNonNull(
    +                                    lastUploadedPluginDir.get().listFiles((file, fileName) ->
    +                                        fileName.startsWith("uploaded")))).
    +                                        max(Comparator.comparingLong(File::lastModified));
    +                        assertTrue(pluginFile.isPresent());
    +                        return true;
    +                    } else {
    +                        return false;
    +                    }
    +                });
    +        assertEquals(EnumSet.of(OWNER_EXECUTE, OWNER_READ, OWNER_WRITE), filesPermission[0]);
    +    }
    +
    +    @TestExtension("verifyUploadedPluginFromURLPermission")
    +    public static final class ReturnPluginJpiAction implements RootAction {
    +
    +        @Override
    +        public String getIconFileName() {
    +            return "gear2.png";
    +        }
    +
    +        @Override
    +        public String getDisplayName() {
    +            return "URL to retrieve a plugin jpi";
    +        }
    +
    +        @Override
    +        public String getUrlName() {
    +            return "pluginManagerGetPlugin";
    +        }
    +
    +        public void doDynamic(StaplerRequest staplerRequest, StaplerResponse staplerResponse) throws ServletException, IOException {
    +            staplerResponse.setContentType("application/octet-stream");
    +            staplerResponse.setStatus(200);
    +            staplerResponse.serveFile(staplerRequest,  PluginManagerTest.class.getClassLoader().getResource("plugins/htmlpublisher.jpi"));
    +        }
    +    }
    +}
    
  • test/src/test/java/hudson/PluginManagerTest.java+19 8 modified
    @@ -24,6 +24,7 @@
     
     package hudson;
     
    +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
     import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
     import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
     import static org.awaitility.Awaitility.await;
    @@ -173,7 +174,7 @@ public String getUrlName() {
             }
     
             public void doDynamic(StaplerRequest staplerRequest, StaplerResponse staplerResponse) throws ServletException, IOException {
    -            staplerResponse.setContentType("application/octet");
    +            staplerResponse.setContentType("application/octet-stream");
                 staplerResponse.setStatus(200);
                 staplerResponse.serveFile(staplerRequest,  PluginManagerTest.class.getClassLoader().getResource("plugins/htmlpublisher.jpi"));
             }
    @@ -743,24 +744,34 @@ public void verifyUploadedPluginPermission() throws Exception {
             File dir = tmp.newFolder();
             File plugin = new File(dir, "htmlpublisher.jpi");
             FileUtils.copyURLToFile(Objects.requireNonNull(getClass().getClassLoader().getResource("plugins/htmlpublisher.jpi")), plugin);
    -        f.getInputByName("name").setValue(plugin.getAbsolutePath());
    +        f.getInputByName("name").setValueAttribute(plugin.getAbsolutePath());
             r.submit(f);
     
    -        File tmpDir = new File(File.createTempFile("tmp", ".tmp").getParent());
    -        tmpDir.deleteOnExit();
    +        File filesRef = Files.createTempFile("tmp", ".tmp").toFile();
    +        File filesTmpDir = filesRef.getParentFile();
    +        filesRef.deleteOnExit();
    +
             final Set<PosixFilePermission>[] filesPermission = new Set[]{new HashSet<>()};
             await().pollInterval(250, TimeUnit.MILLISECONDS)
                     .atMost(10, TimeUnit.SECONDS)
                     .until(() -> {
    -                    Optional<File> lastUploadedPlugin = Arrays.stream(Objects.requireNonNull(tmpDir.listFiles((file, fileName) -> fileName.startsWith("uploaded")))).max(Comparator.comparingLong(File::lastModified));
    -                    if (lastUploadedPlugin.isPresent()) {
    -                        filesPermission[0] = Files.getPosixFilePermissions(lastUploadedPlugin.get().toPath(), LinkOption.NOFOLLOW_LINKS);
    +                    Optional<File> lastUploadedPluginDir = Arrays.stream(Objects.requireNonNull(
    +                                    filesTmpDir.listFiles((file, fileName) ->
    +                                            fileName.startsWith("uploadDir")))).
    +                            max(Comparator.comparingLong(File::lastModified));
    +                    if (lastUploadedPluginDir.isPresent()) {
    +                        filesPermission[0] = Files.getPosixFilePermissions(lastUploadedPluginDir.get().toPath(), LinkOption.NOFOLLOW_LINKS);
    +                        Optional<File> pluginFile = Arrays.stream(Objects.requireNonNull(
    +                                        lastUploadedPluginDir.get().listFiles((file, fileName) ->
    +                                                fileName.startsWith("uploaded")))).
    +                                max(Comparator.comparingLong(File::lastModified));
    +                        assertTrue(pluginFile.isPresent());
                             return true;
                         } else {
                             return false;
                         }
                     });
    -        assertEquals(EnumSet.of(OWNER_READ, OWNER_WRITE), filesPermission[0]);
    +        assertEquals(EnumSet.of(OWNER_EXECUTE, OWNER_READ, OWNER_WRITE), filesPermission[0]);
         }
     
         @Test
    

Vulnerability mechanics

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

References

5

News mentions

1