Jenkins allows attackers to configure restricted projects
Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.
Affected versions
1.481 → fixed in 1.5020 → fixed in 1.480.3
Details
Jenkins before 1.502 allows remote authenticated users to configure an otherwise restricted project via vectors related to post-build actions.
The fix
[SECURITY-55]
core/src/main/java/hudson/model/AbstractProject.java+7 −0
@@ -1914,6 +1914,13 @@ protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOExceptiotriggers = 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);+}+}}/**
core/src/main/java/hudson/model/Descriptor.java+6 −3
@@ -936,7 +936,10 @@ List<T> newInstancesFromHeteroList(StaplerRequest req, Object formData,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));+}}}@@ -946,7 +949,7 @@ List<T> newInstancesFromHeteroList(StaplerRequest req, Object formData,/*** 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;@@ -960,7 +963,7 @@ public static <T extends Descriptor> T find(Collection<? extends T> list, Stringreturn null;}-public static Descriptor find(String className) {+public static @CheckForNull Descriptor find(String className) {return find(Jenkins.getInstance().getExtensionList(Descriptor.class),className);}
core/src/main/java/hudson/tasks/BuildTrigger.java+4 −1
@@ -319,7 +319,7 @@ public boolean showEvenIfUnstableOption(@CheckForNull Class<? extends AbstractPr/*** 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 projectif(!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok();@@ -334,6 +334,9 @@ public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter StriAbstractProject.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;}}
core/src/main/resources/hudson/tasks/Messages.properties+1 −0
@@ -47,6 +47,7 @@ BuildTrigger.NoSuchProject=No such project ''{0}''. Did you mean ''{1}''?BuildTrigger.NoProjectSpecified=No project specifiedBuildTrigger.NotBuildable={0} is not buildableBuildTrigger.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 failedCommandInterpreter.UnableToDelete=Unable to delete script file {0}
core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly+1 −1
@@ -38,7 +38,7 @@ THE SOFTWARE.<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&value='+encodeURIComponent(this.value)"autoCompleteField="upstreamProjects"/></f:entry></f:optionalBlock>
[SECURITY-55]
core/src/main/java/hudson/model/AbstractProject.java+7 −0
@@ -1794,6 +1794,13 @@ protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOExceptiotriggers = 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);+}+}}/**
core/src/main/java/hudson/model/Descriptor.java+7 −3
@@ -72,6 +72,7 @@import java.lang.reflect.Field;import java.lang.reflect.ParameterizedType;import java.beans.Introspector;+import javax.annotation.CheckForNull;/*** Metadata about a configurable instance.@@ -909,7 +910,10 @@ List<T> newInstancesFromHeteroList(StaplerRequest req, Object formData,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));+}}}@@ -919,7 +923,7 @@ List<T> newInstancesFromHeteroList(StaplerRequest req, Object formData,/*** 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;@@ -933,7 +937,7 @@ public static <T extends Descriptor> T find(Collection<? extends T> list, Stringreturn null;}-public static Descriptor find(String className) {+public static @CheckForNull Descriptor find(String className) {return find(Jenkins.getInstance().getExtensionList(Descriptor.class),className);}
core/src/main/java/hudson/tasks/BuildTrigger.java+4 −1
@@ -318,7 +318,7 @@ public boolean showEvenIfUnstableOption(Class<? extends AbstractProject> jobType/*** 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 projectif(!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok();@@ -333,6 +333,9 @@ public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter StriAbstractProject.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;}}
core/src/main/resources/hudson/tasks/Messages.properties+1 −0
@@ -47,6 +47,7 @@ BuildTrigger.NoSuchProject=No such project ''{0}''. Did you mean ''{1}''?BuildTrigger.NoProjectSpecified=No project specifiedBuildTrigger.NotBuildable={0} is not buildableBuildTrigger.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 failedCommandInterpreter.UnableToDelete=Unable to delete script file {0}
core/src/main/resources/lib/hudson/project/config-upstream-pseudo-trigger.jelly+1 −1
@@ -38,7 +38,7 @@ THE SOFTWARE.<f:entry title="${%Projects 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&value='+encodeURIComponent(this.value)"autoCompleteField="upstreamProjects"/></f:entry></f:optionalBlock>
References
- ADVISORYhttps://nvd.nist.gov/vuln/detail/CVE-2013-7330
- WEBhttps://github.com/jenkinsci/jenkins/commit/36342d71e29e0620f803a7470ce96c61761648d8
- WEBhttps://github.com/jenkinsci/jenkins/commit/757bc8a53956e6fbab267214e6e0896f03c3c262
- PACKAGEhttps://github.com/jenkinsci/jenkins
- WEBhttps://wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2014-02-14
- WEBhttp://www.openwall.com/lists/oss-security/2014/02/21/2