CVE-2013-7330: Permissions, Privileges, and Access Controls (CWE-264)
value: Jenkins before 1.502 allows remote authenticated users to configure an otherwise restricted project via vectors related to post-build actions.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2014-10-17T15:55Z
Repository: https://github.com/jenkinsci/jenkins
Language: Java
Files changed: core/src/main/java/hudson/model/AbstractProject.java, core/src/main/java/hudson/model/Descriptor.java, core/src/main/java/hudson/tasks/BuildTrigger.java, core/src/main/resources/hudson/tasks/Messages.properties, core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly
Commit date: 2013-02-13 15:32:09 -0800
Commit message: [SECURITY-55]

This patch makes standard post-build action refuse to let you configure a downstream project you cannot currently build.
The one from parameterized-trigger will show an error in the configure screen but still lets you save the configuration; needs an analogous patch to that plugin.
Does not yet protect against POSTing config.xml with the trigger.
(cherry picked from commit 757bc8a53956e6fbab267214e6e0896f03c3c262)

Conflicts:

	core/src/main/java/hudson/model/Descriptor.java

Diff: diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index e0a0007b70..e6b606b72c 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1912,10 +1912,17 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
         for (Trigger t : triggers())
             t.stop();
         triggers = buildDescribable(req, Trigger.for_(this));
         for (Trigger t : triggers)
             t.start(this,true);
+
+        for (Publisher _t : Descriptor.newInstancesFromHeteroList(req, json, "publisher", Jenkins.getInstance().getExtensionList(BuildTrigger.DescriptorImpl.class))) {
+            BuildTrigger t = (BuildTrigger) _t;
+            for (AbstractProject downstream : t.getChildProjects(this)) {
+                downstream.checkPermission(BUILD);
+            }
+        }
     }
 
     /**
      * @deprecated
      *      As of 1.261. Use {@link #buildDescribable(StaplerRequest, List)} instead.
diff --git a/core/src/main/java/hudson/model/Descriptor.java b/core/src/main/java/hudson/model/Descriptor.java
index bf8e40e992..195c5e4afa 100644
--- a/core/src/main/java/hudson/model/Descriptor.java
+++ b/core/src/main/java/hudson/model/Descriptor.java
@@ -934,21 +934,24 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
 
         if (formData!=null) {
             for (Object o : JSONArray.fromObject(formData)) {
                 JSONObject jo = (JSONObject)o;
                 String kind = jo.getString("kind");
-                items.add(find(descriptors,kind).newInstance(req,jo));
+                Descriptor<T> d = find(descriptors, kind);
+                if (d != null) {
+                    items.add(d.newInstance(req, jo));
+                }
             }
         }
 
         return items;
     }
 
     /**
      * Finds a descriptor from a collection by its class name.
      */
-    public static <T extends Descriptor> T find(Collection<? extends T> list, String className) {
+    public static @CheckForNull <T extends Descriptor> T find(Collection<? extends T> list, String className) {
         for (T d : list) {
             if(d.getClass().getName().equals(className))
                 return d;
         }
         // Since we introduced Descriptor.getId(), it is a preferred method of identifying descriptor by a string.
@@ -958,11 +961,11 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
                 return d;
         }
         return null;
     }
 
-    public static Descriptor find(String className) {
+    public static @CheckForNull Descriptor find(String className) {
         return find(Jenkins.getInstance().getExtensionList(Descriptor.class),className);
     }
 
     public static final class FormException extends Exception implements HttpResponse {
         private final String formField;
diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java
index 3646b55b8e..432b1bbb07 100644
--- a/core/src/main/java/hudson/tasks/BuildTrigger.java
+++ b/core/src/main/java/hudson/tasks/BuildTrigger.java
@@ -317,11 +317,11 @@ public class BuildTrigger extends Recorder implements DependencyDeclarer {
         }
 
         /**
          * Form validation method.
          */
-        public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter String value ) {
+        public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter String value, @QueryParameter boolean upstream) {
             // Require CONFIGURE permission on this project
             if(!project.hasPermission(Item.CONFIGURE))      return FormValidation.ok();
 
             StringTokenizer tokens = new StringTokenizer(Util.fixNull(value),",");
             boolean hasProjects = false;
@@ -332,10 +332,13 @@ public class BuildTrigger extends Recorder implements DependencyDeclarer {
                     if(item==null)
                         return FormValidation.error(Messages.BuildTrigger_NoSuchProject(projectName,
                                 AbstractProject.findNearest(projectName,project.getParent()).getRelativeNameFrom(project)));
                     if(!(item instanceof AbstractProject))
                         return FormValidation.error(Messages.BuildTrigger_NotBuildable(projectName));
+                    if (!upstream && !item.hasPermission(Item.BUILD)) {
+                        return FormValidation.error(Messages.BuildTrigger_you_have_no_permission_to_build_(projectName));
+                    }
                     hasProjects = true;
                 }
             }
             if (!hasProjects) {
                 return FormValidation.error(Messages.BuildTrigger_NoProjectSpecified());
diff --git a/core/src/main/resources/hudson/tasks/Messages.properties b/core/src/main/resources/hudson/tasks/Messages.properties
index 3f61827ddc..2c0a388fa7 100644
--- a/core/src/main/resources/hudson/tasks/Messages.properties
+++ b/core/src/main/resources/hudson/tasks/Messages.properties
@@ -45,10 +45,11 @@ BuildTrigger.DisplayName=Build other projects
 BuildTrigger.InQueue={0} is already in the queue
 BuildTrigger.NoSuchProject=No such project ''{0}''. Did you mean ''{1}''?
 BuildTrigger.NoProjectSpecified=No project specified
 BuildTrigger.NotBuildable={0} is not buildable
 BuildTrigger.Triggering=Triggering a new build of {0}
+BuildTrigger.you_have_no_permission_to_build_=You have no permission to build {0}
 
 CommandInterpreter.CommandFailed=command execution failed
 CommandInterpreter.UnableToDelete=Unable to delete script file {0}
 CommandInterpreter.UnableToProduceScript=Unable to produce a script file
 
diff --git a/core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly b/core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly
index 5ce128e893..9c5737d826 100644
--- a/core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly
+++ b/core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly
@@ -36,10 +36,10 @@ THE SOFTWARE.
                    title="${%Build after other projects are built}" 
                    checked="${!empty(up)}">
     <f:entry title="${%Project names}"
              description="${%Multiple projects can be specified like 'abc, def'}">
       <f:textbox name="upstreamProjects" value="${h.getProjectListString(up)}"
-        checkUrl="'descriptorByName/hudson.tasks.BuildTrigger/check?value='+encodeURIComponent(this.value)"
+        checkUrl="'descriptorByName/hudson.tasks.BuildTrigger/check?upstream=true&amp;value='+encodeURIComponent(this.value)"
         autoCompleteField="upstreamProjects"/>
     </f:entry>
   </f:optionalBlock>
 </j:jelly>

Vulnerable code:                 items.add(find(descriptors,kind).newInstance(req,jo));
    public static <T extends Descriptor> T find(Collection<? extends T> list, String className) {
    public static Descriptor find(String className) {
        public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter String value ) {
        checkUrl="'descriptorByName/hudson.tasks.BuildTrigger/check?value='+encodeURIComponent(this.value)"
Fixed code: 
        for (Publisher _t : Descriptor.newInstancesFromHeteroList(req, json, "publisher", Jenkins.getInstance().getExtensionList(BuildTrigger.DescriptorImpl.class))) {
            BuildTrigger t = (BuildTrigger) _t;
            for (AbstractProject downstream : t.getChildProjects(this)) {
                downstream.checkPermission(BUILD);
            }
        }
                Descriptor<T> d = find(descriptors, kind);
                if (d != null) {
                    items.add(d.newInstance(req, jo));
                }
    public static @CheckForNull <T extends Descriptor> T find(Collection<? extends T> list, String className) {
    public static @CheckForNull Descriptor find(String className) {
        public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter String value, @QueryParameter boolean upstream) {
                    if (!upstream && !item.hasPermission(Item.BUILD)) {
                        return FormValidation.error(Messages.BuildTrigger_you_have_no_permission_to_build_(projectName));
                    }
BuildTrigger.you_have_no_permission_to_build_=You have no permission to build {0}
        checkUrl="'descriptorByName/hudson.tasks.BuildTrigger/check?upstream=true&amp;value='+encodeURIComponent(this.value)"