diff -Nru jenkins-1.424.6+dfsg/debian/changelog jenkins-1.424.6+dfsg/debian/changelog --- jenkins-1.424.6+dfsg/debian/changelog 2012-03-27 11:40:15.000000000 +0100 +++ jenkins-1.424.6+dfsg/debian/changelog 2012-09-25 13:42:29.000000000 +0100 @@ -1,3 +1,16 @@ +jenkins (1.424.6+dfsg-1ubuntu0.1) precise-security; urgency=low + + * SECURITY UPDATE: Remote code execution and XSS vulnerabilities + in Jenkins core (LP: #1055416): + - d/p/security/CVE-2012-4438_CVE-2012-4439.patch: Cherry picked + fixes from 1.466.2 release to resolve remote code execution + and XSS security vulnerabilities. + - http://www.cloudbees.com/jenkins-advisory/jenkins-security-advisory-2012-09-17.cb + - CVE-2012-4438 + - CVE-2012-4439 + + -- James Page Tue, 25 Sep 2012 13:32:05 +0100 + jenkins (1.424.6+dfsg-1) unstable; urgency=low * New upstream release, fixing XSS security vulnerability (Closes: #664057): diff -Nru jenkins-1.424.6+dfsg/debian/control jenkins-1.424.6+dfsg/debian/control --- jenkins-1.424.6+dfsg/debian/control 2012-03-27 11:40:15.000000000 +0100 +++ jenkins-1.424.6+dfsg/debian/control 2012-09-25 13:42:34.000000000 +0100 @@ -1,7 +1,8 @@ Source: jenkins Section: java Priority: optional -Maintainer: Debian Java Maintainers +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Java Maintainers Uploaders: James Page Build-Depends: cdbs, diff -Nru jenkins-1.424.6+dfsg/debian/patches/security/CVE-2012-4438_CVE-2012-4439.patch jenkins-1.424.6+dfsg/debian/patches/security/CVE-2012-4438_CVE-2012-4439.patch --- jenkins-1.424.6+dfsg/debian/patches/security/CVE-2012-4438_CVE-2012-4439.patch 1970-01-01 01:00:00.000000000 +0100 +++ jenkins-1.424.6+dfsg/debian/patches/security/CVE-2012-4438_CVE-2012-4439.patch 2012-09-25 13:40:20.000000000 +0100 @@ -0,0 +1,539 @@ +Decription: Cherry picked fixes from 1.466.2 to resolve + two security issues: + - CVE-2012-4438 jenkins remote code execution + - CVE-2012-4439 jenkins XSS +Origin: Upstream, commits fb73bac50f22526a3d3b...43ff1688eee6ea + +diff --git a/core/src/main/java/hudson/model/DownloadService.java b/core/src/main/java/hudson/model/DownloadService.java +index 5ce63ec..a1f9b78 100644 +--- a/core/src/main/java/hudson/model/DownloadService.java ++++ b/core/src/main/java/hudson/model/DownloadService.java +@@ -26,18 +26,20 @@ package hudson.model; + import hudson.Extension; + import hudson.ExtensionList; + import hudson.ExtensionPoint; ++import hudson.util.FormValidation; ++import hudson.util.FormValidation.Kind; + import hudson.util.IOException2; + import hudson.util.IOUtils; + import hudson.util.QuotedStringTokenizer; + import hudson.util.TextFile; + import hudson.util.TimeUnit2; + import jenkins.model.Jenkins; ++import jenkins.util.JSONSignatureValidator; + import net.sf.json.JSONException; + import org.kohsuke.stapler.Stapler; + + import java.io.File; + import java.io.IOException; +-import java.util.logging.Level; + import java.util.logging.Logger; + + import net.sf.json.JSONObject; +@@ -65,7 +67,8 @@ public class DownloadService extends PageDecorator { + */ + public String generateFragment() { + if (neverUpdate) return ""; +- ++ if (doesNotSupportPostMessage()) return ""; ++ + StringBuilder buf = new StringBuilder(); + if(Jenkins.getInstance().hasPermission(Jenkins.READ)) { + long now = System.currentTimeMillis(); +@@ -92,6 +95,23 @@ public class DownloadService extends PageDecorator { + return buf.toString(); + } + ++ private boolean doesNotSupportPostMessage() { ++ StaplerRequest req = Stapler.getCurrentRequest(); ++ if (req==null) return false; ++ ++ String ua = req.getHeader("User-Agent"); ++ if (ua==null) return false; ++ ++ // according to http://caniuse.com/#feat=x-doc-messaging, IE <=7 doesn't support pstMessage ++ // see http://www.useragentstring.com/pages/Internet%20Explorer/ for user agents ++ ++ // we want to err on the cautious side here. ++ // Because of JENKINS-15105, we can't serve signed metadata from JSON, which means we need to be ++ // using a modern browser as a vehicle to request these data. This check is here to prevent Jenkins ++ // from using older browsers that are known not to support postMessage as the vehicle. ++ return ua.contains("Windows") && (ua.contains(" MSIE 5.") || ua.contains(" MSIE 6.") || ua.contains(" MSIE 7.")); ++ } ++ + private String mapHttps(String url) { + /* + HACKISH: +@@ -227,11 +247,24 @@ public class DownloadService extends PageDecorator { + */ + public void doPostBack(StaplerRequest req, StaplerResponse rsp) throws IOException { + long dataTimestamp = System.currentTimeMillis(); ++ due = dataTimestamp+getInterval(); // success or fail, don't try too often ++ ++ String json = IOUtils.toString(req.getInputStream(),"UTF-8"); ++ JSONObject o = JSONObject.fromObject(json); ++ ++ if (signatureCheck) { ++ FormValidation e = new JSONSignatureValidator("downloadable '"+id+"'").verifySignature(o); ++ if (e.kind!= Kind.OK) { ++ LOGGER.severe(e.renderHtml()); ++ throw e; ++ } ++ } ++ + TextFile df = getDataFile(); +- df.write(IOUtils.toString(req.getInputStream(),"UTF-8")); ++ df.write(json); + df.file.setLastModified(dataTimestamp); +- due = dataTimestamp+getInterval(); + LOGGER.info("Obtained the updated data file for "+id); ++ + rsp.setContentType("text/plain"); // So browser won't try to parse response + } + +@@ -257,5 +290,10 @@ public class DownloadService extends PageDecorator { + } + + public static boolean neverUpdate = Boolean.getBoolean(DownloadService.class.getName()+".never"); ++ ++ /** ++ * Off by default until we know this is reasonably working. ++ */ ++ public static boolean signatureCheck = !Boolean.getBoolean(DownloadService.class.getName()+".noSignatureCheck"); + } + +diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java +index c6ac33b..5d685b5 100644 +--- a/core/src/main/java/hudson/model/UpdateSite.java ++++ b/core/src/main/java/hudson/model/UpdateSite.java +@@ -25,7 +25,6 @@ + + package hudson.model; + +-import com.trilead.ssh2.crypto.Base64; + import hudson.PluginManager; + import hudson.PluginWrapper; + import hudson.lifecycle.Lifecycle; +@@ -37,12 +36,9 @@ import hudson.util.IOUtils; + import hudson.util.TextFile; + import hudson.util.VersionNumber; + import jenkins.model.Jenkins; ++import jenkins.util.JSONSignatureValidator; + import net.sf.json.JSONException; + import net.sf.json.JSONObject; +-import org.apache.commons.io.output.NullOutputStream; +-import org.apache.commons.io.output.TeeOutputStream; +-import org.jvnet.hudson.crypto.CertificateUtil; +-import org.jvnet.hudson.crypto.SignatureOutputStream; + import org.kohsuke.stapler.DataBoundConstructor; + import org.kohsuke.stapler.HttpResponse; + import org.kohsuke.stapler.StaplerRequest; +@@ -56,17 +52,9 @@ import java.io.OutputStreamWriter; + import java.io.Writer; + import java.security.DigestOutputStream; + import java.security.GeneralSecurityException; +-import java.security.MessageDigest; +-import java.security.Signature; +-import java.security.cert.CertificateExpiredException; +-import java.security.cert.CertificateFactory; +-import java.security.cert.CertificateNotYetValidException; +-import java.security.cert.TrustAnchor; +-import java.security.cert.X509Certificate; + import java.util.ArrayList; + import java.util.Collections; + import java.util.HashMap; +-import java.util.HashSet; + import java.util.List; + import java.util.Map; + import java.util.Set; +@@ -172,87 +160,7 @@ public class UpdateSite { + * Verifies the signature in the update center data file. + */ + private FormValidation verifySignature(JSONObject o) throws IOException { +- try { +- FormValidation warning = null; +- +- JSONObject signature = o.getJSONObject("signature"); +- if (signature.isNullObject()) { +- return FormValidation.error("No signature block found in update center '"+id+"'"); +- } +- o.remove("signature"); +- +- List certs = new ArrayList(); +- {// load and verify certificates +- CertificateFactory cf = CertificateFactory.getInstance("X509"); +- for (Object cert : signature.getJSONArray("certificates")) { +- X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray()))); +- try { +- c.checkValidity(); +- } catch (CertificateExpiredException e) { // even if the certificate isn't valid yet, we'll proceed it anyway +- warning = FormValidation.warning(e,String.format("Certificate %s has expired in update center '%s'",cert.toString(),id)); +- } catch (CertificateNotYetValidException e) { +- warning = FormValidation.warning(e,String.format("Certificate %s is not yet valid in update center '%s'",cert.toString(),id)); +- } +- certs.add(c); +- } +- +- // all default root CAs in JVM are trusted, plus certs bundled in Jenkins +- Set anchors = new HashSet(); // CertificateUtil.getDefaultRootCAs(); +- ServletContext context = Jenkins.getInstance().servletContext; +- for (String cert : (Set) context.getResourcePaths("/WEB-INF/update-center-rootCAs")) { +- if (cert.endsWith(".txt")) continue; // skip text files that are meant to be documentation +- anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(context.getResourceAsStream(cert)),null)); +- } +- CertificateUtil.validatePath(certs,anchors); +- } +- +- // this is for computing a digest to check sanity +- MessageDigest sha1 = MessageDigest.getInstance("SHA1"); +- DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1); +- +- // this is for computing a signature +- Signature sig = Signature.getInstance("SHA1withRSA"); +- sig.initVerify(certs.get(0)); +- SignatureOutputStream sos = new SignatureOutputStream(sig); +- +- // until JENKINS-11110 fix, UC used to serve invalid digest (and therefore unverifiable signature) +- // that only covers the earlier portion of the file. This was caused by the lack of close() call +- // in the canonical writing, which apparently leave some bytes somewhere that's not flushed to +- // the digest output stream. This affects Jenkins [1.424,1,431]. +- // Jenkins 1.432 shipped with the "fix" (1eb0c64abb3794edce29cbb1de50c93fa03a8229) that made it +- // compute the correct digest, but it breaks all the existing UC json metadata out there. We then +- // quickly discovered ourselves in the catch-22 situation. If we generate UC with the correct signature, +- // it'll cut off [1.424,1.431] from the UC. But if we don't, we'll cut off [1.432,*). +- // +- // In 1.433, we revisited 1eb0c64abb3794edce29cbb1de50c93fa03a8229 so that the original "digest"/"signature" +- // pair continues to be generated in a buggy form, while "correct_digest"/"correct_signature" are generated +- // correctly. +- // +- // Jenkins should ignore "digest"/"signature" pair. Accepting it creates a vulnerability that allows +- // the attacker to inject a fragment at the end of the json. +- o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")).close(); +- +- // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n +- // (which is more likely than someone tampering with update center), we can tell +- String computedDigest = new String(Base64.encode(sha1.digest())); +- String providedDigest = signature.optString("correct_digest"); +- if (providedDigest==null) { +- return FormValidation.error("No correct_digest parameter in update center '"+id+"'. This metadata appears to be old."); +- } +- if (!computedDigest.equalsIgnoreCase(providedDigest)) { +- return FormValidation.error("Digest mismatch: "+computedDigest+" vs "+providedDigest+" in update center '"+id+"'"); +- } +- +- String providedSignature = signature.getString("correct_signature"); +- if (!sig.verify(Base64.decode(providedSignature.toCharArray()))) { +- return FormValidation.error("Signature in the update center doesn't match with the certificate in update center '"+id+"'"); +- } +- +- if (warning!=null) return warning; +- return FormValidation.ok(); +- } catch (GeneralSecurityException e) { +- return FormValidation.error(e,"Signature verification failed in the update center '"+id+"'"); +- } ++ return new JSONSignatureValidator("update site '"+id+"'").verifySignature(o); + } + + /** +diff --git a/core/src/main/java/hudson/search/Search.java b/core/src/main/java/hudson/search/Search.java +index 55737b8..f439d8a 100644 +--- a/core/src/main/java/hudson/search/Search.java ++++ b/core/src/main/java/hudson/search/Search.java +@@ -84,6 +84,7 @@ public class Search { + * See http://developer.mozilla.org/en/docs/Supporting_search_suggestions_in_search_plugins + */ + public void doSuggestOpenSearch(StaplerRequest req, StaplerResponse rsp, @QueryParameter String q) throws IOException, ServletException { ++ rsp.setContentType(Flavor.JSON.contentType); + DataWriter w = Flavor.JSON.createDataWriter(null, rsp); + w.startArray(); + w.value(q); +diff --git a/core/src/main/java/hudson/tasks/junit/History.java b/core/src/main/java/hudson/tasks/junit/History.java +index 6d5333c..7a46b60 100644 +--- a/core/src/main/java/hudson/tasks/junit/History.java ++++ b/core/src/main/java/hudson/tasks/junit/History.java +@@ -293,4 +293,12 @@ public class History { + + } + ++ public static int asInt(String s, int defalutValue) { ++ if (s==null) return defalutValue; ++ try { ++ return Integer.parseInt(s); ++ } catch (NumberFormatException e) { ++ return defalutValue; ++ } ++ } + } +diff --git a/core/src/main/java/jenkins/util/JSONSignatureValidator.java b/core/src/main/java/jenkins/util/JSONSignatureValidator.java +new file mode 100644 +index 0000000..69c6144 +--- /dev/null ++++ b/core/src/main/java/jenkins/util/JSONSignatureValidator.java +@@ -0,0 +1,140 @@ ++package jenkins.util; ++ ++import com.trilead.ssh2.crypto.Base64; ++import hudson.util.FormValidation; ++import jenkins.model.Jenkins; ++import net.sf.json.JSONObject; ++import org.apache.commons.io.output.NullOutputStream; ++import org.apache.commons.io.output.TeeOutputStream; ++import org.jvnet.hudson.crypto.CertificateUtil; ++import org.jvnet.hudson.crypto.SignatureOutputStream; ++ ++import java.io.ByteArrayInputStream; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.IOException; ++import java.io.OutputStreamWriter; ++import java.security.DigestOutputStream; ++import java.security.GeneralSecurityException; ++import java.security.MessageDigest; ++import java.security.Signature; ++import java.security.cert.CertificateExpiredException; ++import java.security.cert.CertificateFactory; ++import java.security.cert.CertificateNotYetValidException; ++import java.security.cert.TrustAnchor; ++import java.security.cert.X509Certificate; ++import java.util.ArrayList; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Set; ++ ++/** ++ * @author Kohsuke Kawaguchi ++ */ ++public class JSONSignatureValidator { ++ private final String name; ++ ++ public JSONSignatureValidator(String name) { ++ this.name = name; ++ } ++ ++ /** ++ * Verifies the signature in the update center data file. ++ */ ++ public FormValidation verifySignature(JSONObject o) throws IOException { ++ try { ++ FormValidation warning = null; ++ ++ JSONObject signature = o.getJSONObject("signature"); ++ if (signature.isNullObject()) { ++ return FormValidation.error("No signature block found in "+name); ++ } ++ o.remove("signature"); ++ ++ List certs = new ArrayList(); ++ {// load and verify certificates ++ CertificateFactory cf = CertificateFactory.getInstance("X509"); ++ for (Object cert : signature.getJSONArray("certificates")) { ++ X509Certificate c = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(cert.toString().toCharArray()))); ++ try { ++ c.checkValidity(); ++ } catch (CertificateExpiredException e) { // even if the certificate isn't valid yet, we'll proceed it anyway ++ warning = FormValidation.warning(e,String.format("Certificate %s has expired in %s",cert.toString(),name)); ++ } catch (CertificateNotYetValidException e) { ++ warning = FormValidation.warning(e,String.format("Certificate %s is not yet valid in %s",cert.toString(),name)); ++ } ++ certs.add(c); ++ } ++ ++ // if we trust default root CAs, we end up trusting anyone who has a valid certificate, ++ // which isn't useful at all ++ Set anchors = new HashSet(); // CertificateUtil.getDefaultRootCAs(); ++ Jenkins j = Jenkins.getInstance(); ++ for (String cert : (Set) j.servletContext.getResourcePaths("/WEB-INF/update-center-rootCAs")) { ++ if (cert.endsWith(".txt")) continue; // skip text files that are meant to be documentation ++ anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(j.servletContext.getResourceAsStream(cert)),null)); ++ } ++ File[] cas = new File(j.root, "update-center-rootCAs").listFiles(); ++ if (cas!=null) { ++ for (File cert : cas) { ++ if (cert.getName().endsWith(".txt")) continue; // skip text files that are meant to be documentation ++ FileInputStream in = new FileInputStream(cert); ++ try { ++ anchors.add(new TrustAnchor((X509Certificate)cf.generateCertificate(in),null)); ++ } finally { ++ in.close(); ++ } ++ } ++ } ++ CertificateUtil.validatePath(certs, anchors); ++ } ++ ++ // this is for computing a digest to check sanity ++ MessageDigest sha1 = MessageDigest.getInstance("SHA1"); ++ DigestOutputStream dos = new DigestOutputStream(new NullOutputStream(),sha1); ++ ++ // this is for computing a signature ++ Signature sig = Signature.getInstance("SHA1withRSA"); ++ sig.initVerify(certs.get(0)); ++ SignatureOutputStream sos = new SignatureOutputStream(sig); ++ ++ // until JENKINS-11110 fix, UC used to serve invalid digest (and therefore unverifiable signature) ++ // that only covers the earlier portion of the file. This was caused by the lack of close() call ++ // in the canonical writing, which apparently leave some bytes somewhere that's not flushed to ++ // the digest output stream. This affects Jenkins [1.424,1,431]. ++ // Jenkins 1.432 shipped with the "fix" (1eb0c64abb3794edce29cbb1de50c93fa03a8229) that made it ++ // compute the correct digest, but it breaks all the existing UC json metadata out there. We then ++ // quickly discovered ourselves in the catch-22 situation. If we generate UC with the correct signature, ++ // it'll cut off [1.424,1.431] from the UC. But if we don't, we'll cut off [1.432,*). ++ // ++ // In 1.433, we revisited 1eb0c64abb3794edce29cbb1de50c93fa03a8229 so that the original "digest"/"signature" ++ // pair continues to be generated in a buggy form, while "correct_digest"/"correct_signature" are generated ++ // correctly. ++ // ++ // Jenkins should ignore "digest"/"signature" pair. Accepting it creates a vulnerability that allows ++ // the attacker to inject a fragment at the end of the json. ++ o.writeCanonical(new OutputStreamWriter(new TeeOutputStream(dos,sos),"UTF-8")).close(); ++ ++ // did the digest match? this is not a part of the signature validation, but if we have a bug in the c14n ++ // (which is more likely than someone tampering with update center), we can tell ++ String computedDigest = new String(Base64.encode(sha1.digest())); ++ String providedDigest = signature.optString("correct_digest"); ++ if (providedDigest==null) { ++ return FormValidation.error("No correct_digest parameter in "+name+". This metadata appears to be old."); ++ } ++ if (!computedDigest.equalsIgnoreCase(providedDigest)) { ++ return FormValidation.error("Digest mismatch: "+computedDigest+" vs "+providedDigest+" in "+name); ++ } ++ ++ String providedSignature = signature.getString("correct_signature"); ++ if (!sig.verify(Base64.decode(providedSignature.toCharArray()))) { ++ return FormValidation.error("Signature in the update center doesn't match with the certificate in "+name); ++ } ++ ++ if (warning!=null) return warning; ++ return FormValidation.ok(); ++ } catch (GeneralSecurityException e) { ++ return FormValidation.error(e,"Signature verification failed in "+name); ++ } ++ } ++} +diff --git a/core/src/main/resources/hudson/tasks/junit/History/index.jelly b/core/src/main/resources/hudson/tasks/junit/History/index.jelly +index 8ad7ee4..cda7a24 100644 +--- a/core/src/main/resources/hudson/tasks/junit/History/index.jelly ++++ b/core/src/main/resources/hudson/tasks/junit/History/index.jelly +@@ -26,8 +26,8 @@ THE SOFTWARE. + + + +- +- ++ ++ + +