CVE-2017-1000362
Description
The re-key admin monitor was introduced in Jenkins 1.498 and re-encrypted all secrets in JENKINS_HOME with a new key. It also created a backup directory with all old secrets, and the key used to encrypt them. These backups were world-readable and not removed afterwards. Jenkins now deletes the backup directory, if present. Upgrading from before 1.498 will no longer create a backup directory. Administrators relying on file access permissions in their manually created backups are advised to check them for the directory $JENKINS_HOME/jenkins.security.RekeySecretAdminMonitor/backups, and delete it if present.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.main:jenkins-coreMaven | >= 1.498, < 2.32.2 | 2.32.2 |
org.jenkins-ci.main:jenkins-coreMaven | >= 2.40, < 2.44 | 2.44 |
Patches
20be33cf7328fMerge pull request #94 from jenkinsci-cert/SECURITY-376
3 files changed · +20 −27
core/src/main/java/hudson/util/SecretRewriter.java+15 −23 modified@@ -2,7 +2,6 @@ import com.trilead.ssh2.crypto.Base64; import hudson.model.TaskListener; -import org.apache.commons.io.FileUtils; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -33,21 +32,21 @@ public class SecretRewriter { */ private int count; - /** - * If non-null the original file before rewrite gets in here. - */ - private final File backupDirectory; - /** * Canonical paths of the directories we are recursing to protect * against symlink induced cycles. */ private Set<String> callstack = new HashSet<String>(); - public SecretRewriter(File backupDirectory) throws GeneralSecurityException { + public SecretRewriter() throws GeneralSecurityException { cipher = Secret.getCipher("AES"); key = Secret.getLegacyKey(); - this.backupDirectory = backupDirectory; + } + + /** @deprecated SECURITY-376: {@code backupDirectory} is ignored */ + @Deprecated + public SecretRewriter(File backupDirectory) throws GeneralSecurityException { + this(); } private String tryRewrite(String s) throws IOException, InvalidKeyException { @@ -70,12 +69,14 @@ private String tryRewrite(String s) throws IOException, InvalidKeyException { return s; } - /** - * @param backup - * if non-null, the original file will be copied here before rewriting. - * if the rewrite doesn't happen, no copying. - */ + /** @deprecated SECURITY-376: {@code backup} is ignored */ + @Deprecated public boolean rewrite(File f, File backup) throws InvalidKeyException, IOException { + return rewrite(f); + } + + public boolean rewrite(File f) throws InvalidKeyException, IOException { + AtomicFileWriter w = new AtomicFileWriter(f, "UTF-8"); try { PrintWriter out = new PrintWriter(new BufferedWriter(w)); @@ -117,10 +118,6 @@ public boolean rewrite(File f, File backup) throws InvalidKeyException, IOExcept } if (modified) { - if (backup!=null) { - backup.getParentFile().mkdirs(); - FileUtils.copyFile(f,backup); - } w.commit(); } return modified; @@ -165,11 +162,7 @@ private int rewriteRecursive(File dir, String relative, TaskListener listener) t if ((count++)%100==0) listener.getLogger().println("Scanning "+child); try { - File backup = null; - if (backupDirectory!=null) backup = new File(backupDirectory,relative+'/'+ cn); - if (rewrite(child,backup)) { - if (backup!=null) - listener.getLogger().println("Copied "+child+" to "+backup+" as a backup"); + if (rewrite(child)) { listener.getLogger().println("Rewritten "+child); rewritten++; } @@ -199,7 +192,6 @@ protected boolean isIgnoredDir(File dir) { String n = dir.getName(); return n.equals("workspace") || n.equals("artifacts") || n.equals("plugins") // no mutable data here - || n.equals("jenkins.security.RekeySecretAdminMonitor") // we don't want to rewrite backups || n.equals(".") || n.equals(".."); }
core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java+4 −1 modified@@ -1,6 +1,7 @@ package jenkins.security; import hudson.Extension; +import hudson.Util; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.model.TaskListener; @@ -50,6 +51,7 @@ public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor { */ private final FileBoolean scanOnBoot = state("scanOnBoot"); + @SuppressWarnings("OverridableMethodCallInConstructor") // should have been final public RekeySecretAdminMonitor() throws IOException { // if JENKINS_HOME existed <1.497, we need to offer rewrite // this computation needs to be done and the value be captured, @@ -59,6 +61,7 @@ public RekeySecretAdminMonitor() throws IOException { if (j.isUpgradedFromBefore(new VersionNumber("1.496.*")) && new FileBoolean(new File(j.getRootDir(),"secret.key.not-so-secret")).isOff()) needed.on(); + Util.deleteRecursive(new File(getBaseDir(), "backups")); // SECURITY-376: no longer used } @Override @@ -133,7 +136,7 @@ protected File getLogFile() { protected void fix(TaskListener listener) throws Exception { LOGGER.info("Initiating a re-keying of secrets. See "+getLogFile()); - SecretRewriter rewriter = new SecretRewriter(new File(getBaseDir(),"backups")); + SecretRewriter rewriter = new SecretRewriter(); try { PrintStream log = listener.getLogger();
core/src/test/groovy/hudson/util/SecretRewriterTest.groovy+1 −3 modified@@ -70,8 +70,7 @@ class SecretRewriterTest { */ @Test void recursionDetection() { - def backup = tmp.newFolder("backup") - def sw = new SecretRewriter(backup); + def sw = new SecretRewriter(); def st = StreamTaskListener.fromStdout() def o = encryptOld("Hello world") @@ -101,7 +100,6 @@ class SecretRewriterTest { dirs.each { p-> assert new File(t,"$p/foo.xml").text.trim()==answer - assert new File(backup,"$p/foo.xml").text.trim()==payload } // t2 is only reachable by following a symlink. this should be covered, too
a572450f039f[SECURITY-376] Remove backup directory for RekeySecretAdminMonitor.
3 files changed · +20 −27
core/src/main/java/hudson/util/SecretRewriter.java+15 −23 modified@@ -2,7 +2,6 @@ import com.trilead.ssh2.crypto.Base64; import hudson.model.TaskListener; -import org.apache.commons.io.FileUtils; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -33,21 +32,21 @@ public class SecretRewriter { */ private int count; - /** - * If non-null the original file before rewrite gets in here. - */ - private final File backupDirectory; - /** * Canonical paths of the directories we are recursing to protect * against symlink induced cycles. */ private Set<String> callstack = new HashSet<String>(); - public SecretRewriter(File backupDirectory) throws GeneralSecurityException { + public SecretRewriter() throws GeneralSecurityException { cipher = Secret.getCipher("AES"); key = Secret.getLegacyKey(); - this.backupDirectory = backupDirectory; + } + + /** @deprecated SECURITY-376: {@code backupDirectory} is ignored */ + @Deprecated + public SecretRewriter(File backupDirectory) throws GeneralSecurityException { + this(); } private String tryRewrite(String s) throws IOException, InvalidKeyException { @@ -70,12 +69,14 @@ private String tryRewrite(String s) throws IOException, InvalidKeyException { return s; } - /** - * @param backup - * if non-null, the original file will be copied here before rewriting. - * if the rewrite doesn't happen, no copying. - */ + /** @deprecated SECURITY-376: {@code backup} is ignored */ + @Deprecated public boolean rewrite(File f, File backup) throws InvalidKeyException, IOException { + return rewrite(f); + } + + public boolean rewrite(File f) throws InvalidKeyException, IOException { + AtomicFileWriter w = new AtomicFileWriter(f, "UTF-8"); try { PrintWriter out = new PrintWriter(new BufferedWriter(w)); @@ -117,10 +118,6 @@ public boolean rewrite(File f, File backup) throws InvalidKeyException, IOExcept } if (modified) { - if (backup!=null) { - backup.getParentFile().mkdirs(); - FileUtils.copyFile(f,backup); - } w.commit(); } return modified; @@ -165,11 +162,7 @@ private int rewriteRecursive(File dir, String relative, TaskListener listener) t if ((count++)%100==0) listener.getLogger().println("Scanning "+child); try { - File backup = null; - if (backupDirectory!=null) backup = new File(backupDirectory,relative+'/'+ cn); - if (rewrite(child,backup)) { - if (backup!=null) - listener.getLogger().println("Copied "+child+" to "+backup+" as a backup"); + if (rewrite(child)) { listener.getLogger().println("Rewritten "+child); rewritten++; } @@ -199,7 +192,6 @@ protected boolean isIgnoredDir(File dir) { String n = dir.getName(); return n.equals("workspace") || n.equals("artifacts") || n.equals("plugins") // no mutable data here - || n.equals("jenkins.security.RekeySecretAdminMonitor") // we don't want to rewrite backups || n.equals(".") || n.equals(".."); }
core/src/main/java/jenkins/security/RekeySecretAdminMonitor.java+4 −1 modified@@ -1,6 +1,7 @@ package jenkins.security; import hudson.Extension; +import hudson.Util; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.model.TaskListener; @@ -50,6 +51,7 @@ public class RekeySecretAdminMonitor extends AsynchronousAdministrativeMonitor { */ private final FileBoolean scanOnBoot = state("scanOnBoot"); + @SuppressWarnings("OverridableMethodCallInConstructor") // should have been final public RekeySecretAdminMonitor() throws IOException { // if JENKINS_HOME existed <1.497, we need to offer rewrite // this computation needs to be done and the value be captured, @@ -59,6 +61,7 @@ public RekeySecretAdminMonitor() throws IOException { if (j.isUpgradedFromBefore(new VersionNumber("1.496.*")) && new FileBoolean(new File(j.getRootDir(),"secret.key.not-so-secret")).isOff()) needed.on(); + Util.deleteRecursive(new File(getBaseDir(), "backups")); // SECURITY-376: no longer used } @Override @@ -133,7 +136,7 @@ protected File getLogFile() { protected void fix(TaskListener listener) throws Exception { LOGGER.info("Initiating a re-keying of secrets. See "+getLogFile()); - SecretRewriter rewriter = new SecretRewriter(new File(getBaseDir(),"backups")); + SecretRewriter rewriter = new SecretRewriter(); try { PrintStream log = listener.getLogger();
core/src/test/groovy/hudson/util/SecretRewriterTest.groovy+1 −3 modified@@ -70,8 +70,7 @@ class SecretRewriterTest { */ @Test void recursionDetection() { - def backup = tmp.newFolder("backup") - def sw = new SecretRewriter(backup); + def sw = new SecretRewriter(); def st = StreamTaskListener.fromStdout() def o = encryptOld("Hello world") @@ -101,7 +100,6 @@ class SecretRewriterTest { dirs.each { p-> assert new File(t,"$p/foo.xml").text.trim()==answer - assert new File(backup,"$p/foo.xml").text.trim()==payload } // t2 is only reachable by following a symlink. this should be covered, too
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
6- github.com/advisories/GHSA-92mr-4w2q-4578ghsaADVISORY
- jenkins.io/security/advisory/2017-02-01/nvdVendor Advisory
- nvd.nist.gov/vuln/detail/CVE-2017-1000362ghsaADVISORY
- github.com/jenkinsci/jenkins/commit/0be33cf7328fad6a7596ce9505a74561a8b1eb85ghsaWEB
- github.com/jenkinsci/jenkins/commit/a572450f039fdb99410fcf6eb0ba307bd69ea458ghsaWEB
- jenkins.io/security/advisory/2017-02-01ghsaWEB
News mentions
0No linked articles in our index yet.