CVE-2013-0158: Insufficient Information (NVD-CWE-noinfo)
value: Unspecified vulnerability in Jenkins before 1.498, Jenkins LTS before 1.480.2, and Jenkins Enterprise 1.447.x before 1.447.6.1 and 1.466.x before 1.466.12.1, when a slave is attached and anonymous read access is enabled, allows remote attackers to obtain the master cryptographic key via unknown vectors.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2013-02-24T22:55Z
Repository: https://github.com/jenkinsci/jenkins
Language: Java
Files changed: core/src/main/java/jenkins/security/ApiTokenProperty.java, test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
Commit date: 2013-01-05 16:55:07 -0800
Commit message: [SECURITY-49] actively invalidate bad API tokens.

If the user still has the API token that's generated from secret.key,
don't accept that.

Hopefully this is the last fix

Diff: diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java
index f1e06efa2..caa9ed885 100644
--- a/core/src/main/java/jenkins/security/ApiTokenProperty.java
+++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java
@@ -29,10 +29,11 @@ import hudson.model.Descriptor.FormException;
 import hudson.model.User;
 import hudson.model.UserProperty;
 import hudson.model.UserPropertyDescriptor;
 import hudson.util.HttpResponses;
 import hudson.util.Secret;
+import jenkins.model.Jenkins;
 import net.sf.json.JSONObject;
 import org.kohsuke.stapler.AncestorInPath;
 import org.kohsuke.stapler.DataBoundConstructor;
 import org.kohsuke.stapler.HttpResponse;
 import org.kohsuke.stapler.StaplerRequest;
@@ -59,16 +60,22 @@ public class ApiTokenProperty extends UserProperty {
 
     /**
      * We don't let the external code set the API token,
      * but for the initial value of the token we need to compute the seed by ourselves.
      */
-    private ApiTokenProperty(String seed) {
+    /*package*/ ApiTokenProperty(String seed) {
         apiToken = Secret.fromString(seed);
     }
 
     public String getApiToken() {
-        return Util.getDigestOf(apiToken.getPlainText());
+        String p = apiToken.getPlainText();
+        if (p.equals(Util.getDigestOf(Jenkins.getInstance().getSecretKey()+":"+user.getId()))) {
+            // if the current token is the initial value created by pre SECURITY-49 Jenkins, we can't use that.
+            // force using the newer value
+            apiToken = Secret.fromString(p=API_KEY_SEED.mac(user.getId()));
+        }
+        return Util.getDigestOf(p);
     }
 
     public boolean matchesPassword(String password) {
         return getApiToken().equals(password);
     }
diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
index 1a71272d1..cf4e85dcf 100644
--- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
+++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java
@@ -1,11 +1,13 @@
 package jenkins.security;
 
 import com.gargoylesoftware.htmlunit.HttpWebConnection;
 import com.gargoylesoftware.htmlunit.html.HtmlForm;
 import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import hudson.Util;
 import hudson.model.User;
+import jenkins.model.Jenkins;
 import org.apache.commons.httpclient.Credentials;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.UsernamePasswordCredentials;
 import org.apache.commons.httpclient.auth.AuthScheme;
 import org.apache.commons.httpclient.auth.AuthScope;
@@ -58,6 +60,29 @@ public class ApiTokenPropertyTest extends HudsonTestCase {
             public User call() throws Exception {
                 return User.current();
             }
         }));
     }
+
+    public void testSecurity49Upgrade() throws Exception {
+        jenkins.setSecurityRealm(createDummySecurityRealm());
+        User u = User.get("foo");
+        String historicalInitialValue = Util.getDigestOf(Jenkins.getInstance().getSecretKey() + ":" + u.getId());
+
+        // we won't accept historically used initial value as it may be compromised
+        ApiTokenProperty t = new ApiTokenProperty(historicalInitialValue);
+        u.addProperty(t);
+        String apiToken1 = t.getApiToken();
+        assertFalse(apiToken1.equals(Util.getDigestOf(historicalInitialValue)));
+
+        // the replacement for the compromised value must be consistent and cannot be random
+        ApiTokenProperty t2 = new ApiTokenProperty(historicalInitialValue);
+        u.addProperty(t2);
+        assertEquals(apiToken1,t2.getApiToken());
+
+        // any other value is OK. those are changed values
+        t = new ApiTokenProperty(historicalInitialValue+"somethingElse");
+        u.addProperty(t);
+        assertTrue(t.getApiToken().equals(Util.getDigestOf(historicalInitialValue+"somethingElse")));
+
+    }
 }

Vulnerable code:     private ApiTokenProperty(String seed) {
        return Util.getDigestOf(apiToken.getPlainText());
Fixed code: import jenkins.model.Jenkins;
    /*package*/ ApiTokenProperty(String seed) {
        String p = apiToken.getPlainText();
        if (p.equals(Util.getDigestOf(Jenkins.getInstance().getSecretKey()+":"+user.getId()))) {
            // if the current token is the initial value created by pre SECURITY-49 Jenkins, we can't use that.
            // force using the newer value
            apiToken = Secret.fromString(p=API_KEY_SEED.mac(user.getId()));
        }
        return Util.getDigestOf(p);
import hudson.Util;
import jenkins.model.Jenkins;

    public void testSecurity49Upgrade() throws Exception {
        jenkins.setSecurityRealm(createDummySecurityRealm());
        User u = User.get("foo");
        String historicalInitialValue = Util.getDigestOf(Jenkins.getInstance().getSecretKey() + ":" + u.getId());

        // we won't accept historically used initial value as it may be compromised
        ApiTokenProperty t = new ApiTokenProperty(historicalInitialValue);
        u.addProperty(t);
        String apiToken1 = t.getApiToken();
        assertFalse(apiToken1.equals(Util.getDigestOf(historicalInitialValue)));

        // the replacement for the compromised value must be consistent and cannot be random
        ApiTokenProperty t2 = new ApiTokenProperty(historicalInitialValue);
        u.addProperty(t2);
        assertEquals(apiToken1,t2.getApiToken());

        // any other value is OK. those are changed values
        t = new ApiTokenProperty(historicalInitialValue+"somethingElse");
        u.addProperty(t);
        assertTrue(t.getApiToken().equals(Util.getDigestOf(historicalInitialValue+"somethingElse")));

    }