Security context
MediumGHSA-h5jv-hg68-mjhg CVE-2013-7330Published May 17, 2022

Jenkins allows attackers to configure restricted projects

Research this vulnerability

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]

Jesse Glick· Feb 11, 2013, 09:47 PM+19536342d71e2
core/src/main/java/hudson/model/AbstractProject.java+7 0
@@ -1914,6 +1914,13 @@ protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
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);
+ }
+ }
}
/**
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, String
return 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 project
if(!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok();
@@ -334,6 +334,9 @@ public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter Stri
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;
}
}
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 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}
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&amp;value='+encodeURIComponent(this.value)"
autoCompleteField="upstreamProjects"/>
</f:entry>
</f:optionalBlock>

[SECURITY-55]

Jesse Glick· Feb 11, 2013, 09:47 PM+205757bc8a539
core/src/main/java/hudson/model/AbstractProject.java+7 0
@@ -1794,6 +1794,13 @@ protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOExceptio
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);
+ }
+ }
}
/**
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, String
return 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 project
if(!project.hasPermission(Item.CONFIGURE)) return FormValidation.ok();
@@ -333,6 +333,9 @@ public FormValidation doCheck(@AncestorInPath Item project, @QueryParameter Stri
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;
}
}
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 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}
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&amp;value='+encodeURIComponent(this.value)"
autoCompleteField="upstreamProjects"/>
</f:entry>
</f:optionalBlock>

References