CVE-2021-21632
Description
A missing permission check in Jenkins OWASP Dependency-Track Plugin 3.1.0 and earlier lets attackers with Overall/Read capture Jenkins credentials via attacker-specified URLs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A missing permission check in Jenkins OWASP Dependency-Track Plugin 3.1.0 and earlier lets attackers with Overall/Read capture Jenkins credentials via attacker-specified URLs.
Vulnerability
Description
The Jenkins OWASP Dependency-Track Plugin, versions 3.1.0 and earlier, contains a missing permission check that allows attackers with Overall/Read permission to connect to an attacker-specified URL [1][3]. This vulnerability arises from insufficient authorization enforcement when the plugin performs network requests to external services, enabling credential capture from Jenkins.
Exploitation
An attacker can exploit this issue by leveraging the Overall/Read permission (a low-level default permission for many users) to configure the plugin to connect to a malicious URL they control [1]. Since the plugin is designed to interact with Dependency-Track servers, the missing check allows directing those connections to an attacker-chosen endpoint without proper authentication validation.
Impact
Successful exploitation reveals credentials stored in Jenkins, such as API keys or other secrets used in the OWASP Dependency-Track Plugin configuration [1][3]. The captured credentials could then be reused to access other systems or escalate privileges within Jenkins environments.
Mitigation
Jenkins released OWASP Dependency-Track Plugin version 3.1.1 to fix the missing permission check [4]. All users should upgrade to this version or later. No workaround is mentioned, and the vulnerability is not known to be listed in CISA KEV.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:dependency-trackMaven | < 3.1.1 | 3.1.1 |
Affected products
2- Range: 1.1.0
Patches
170e7b82ad9a1[SECURITY-2250]
5 files changed · +37 −11
CHANGELOG.md+3 −0 modified@@ -1,6 +1,9 @@ # Dependency-Track Jenkins Plugin - Changelog ## Unreleased +## v3.1.1 - 2021-03-30 +### 🐞 Bugs Fixed +- [SECURITY-2250](https://issues.jenkins.io/browse/SECURITY-2250). Thanks to Justin Philip for reporting this issue. ## v3.1.0 - 2021-02-08 ### ⭐ New Features
src/main/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImpl.java+26 −3 modified@@ -46,6 +46,7 @@ import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.verb.POST; /** * <p> @@ -149,6 +150,7 @@ public boolean isApplicable(Class<? extends AbstractProject> aClass) { * @param item used to lookup credentials in job config. ignored in global * @return ListBoxModel */ + @POST public ListBoxModel doFillProjectIdItems(@QueryParameter final String dependencyTrackUrl, @QueryParameter final String dependencyTrackApiKey, @AncestorInPath @Nullable Item item) { final ListBoxModel projects = new ListBoxModel(); try { @@ -169,6 +171,7 @@ public ListBoxModel doFillProjectIdItems(@QueryParameter final String dependency return projects; } + @POST public ListBoxModel doFillDependencyTrackApiKeyItems(@QueryParameter String credentialsId, @AncestorInPath Item item) { StandardListBoxModel result = new StandardListBoxModel(); if (item == null) { @@ -190,19 +193,33 @@ public ListBoxModel doFillDependencyTrackApiKeyItems(@QueryParameter String cred * Performs input validation when submitting the global config * * @param value The value of the URL as specified in the global config + * @param item used to check permissions in job config. ignored in global * @return a FormValidation object */ - public FormValidation doCheckDependencyTrackUrl(@QueryParameter String value) { + @POST + public FormValidation doCheckDependencyTrackUrl(@QueryParameter String value, @AncestorInPath @Nullable Item item) { + if (item == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + item.checkPermission(Item.CONFIGURE); + } return PluginUtil.doCheckUrl(value); } /** * Performs input validation when submitting the global config * * @param value The value of the URL as specified in the global config + * @param item used to check permissions in job config. ignored in global * @return a FormValidation object */ - public FormValidation doCheckDependencyTrackFrontendUrl(@QueryParameter String value) { + @POST + public FormValidation doCheckDependencyTrackFrontendUrl(@QueryParameter String value, @AncestorInPath @Nullable Item item) { + if (item == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + item.checkPermission(Item.CONFIGURE); + } return PluginUtil.doCheckUrl(value); } @@ -217,12 +234,18 @@ public FormValidation doCheckDependencyTrackFrontendUrl(@QueryParameter String v * config * @return FormValidation */ + @POST public FormValidation doTestConnection(@QueryParameter final String dependencyTrackUrl, @QueryParameter final String dependencyTrackApiKey, @AncestorInPath @Nullable Item item) { + if (item == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + item.checkPermission(Item.CONFIGURE); + } // url may come from instance-config. if empty, then take it from global config (this) final String url = Optional.ofNullable(PluginUtil.parseBaseUrl(dependencyTrackUrl)).orElse(getDependencyTrackUrl()); // api-key may come from instance-config. if empty, then take it from global config (this) final String apiKey = lookupApiKey(Optional.ofNullable(StringUtils.trimToNull(dependencyTrackApiKey)).orElse(getDependencyTrackApiKey()), item); - if (doCheckDependencyTrackUrl(url).kind == FormValidation.Kind.OK && StringUtils.isNotBlank(apiKey)) { + if (doCheckDependencyTrackUrl(url, item).kind == FormValidation.Kind.OK && StringUtils.isNotBlank(apiKey)) { try { final ApiClient apiClient = getClient(url, apiKey); final String result = apiClient.testConnection();
src/main/resources/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher/config.jelly+2 −2 modified@@ -39,10 +39,10 @@ limitations under the License. <f:optionalBlock inline="true" field="overrideGlobals" title="${%overrideGlobals}"> <f:entry title="${%dependencytrack.url}" field="dependencyTrackUrl" help="/plugin/dependency-track/help-dt-url.html"> - <f:textbox id="dependencytrack.url" /> + <f:textbox id="dependencytrack.url" checkMethod="post" /> </f:entry> <f:entry title="${%dependencytrack.url.frontend}" field="dependencyTrackFrontendUrl" help="/plugin/dependency-track/help-dt-url-frontend.html"> - <f:textbox id="dependencytrack.url.frontend"/> + <f:textbox id="dependencytrack.url.frontend" checkMethod="post" /> </f:entry> <f:entry title="${%dependencytrack.apikey}" field="dependencyTrackApiKey" help="/plugin/dependency-track/help-dt-apikey.html"> <c:select id="dependencytrack.apikey" />
src/main/resources/org/jenkinsci/plugins/DependencyTrack/DependencyTrackPublisher/global.jelly+2 −2 modified@@ -18,7 +18,7 @@ limitations under the License. <f:section title="Dependency-Track"> <f:entry title="${%dependencytrack.url}" field="dependencyTrackUrl" help="/plugin/dependency-track/help-dt-url.html"> - <f:textbox id="dependencytrack.url"/> + <f:textbox id="dependencytrack.url" checkMethod="post" /> </f:entry> <f:entry title="${%dependencytrack.apikey}" field="dependencyTrackApiKey" help="/plugin/dependency-track/help-dt-apikey.html"> <c:select id="dependencytrack.apikey" /> @@ -28,7 +28,7 @@ limitations under the License. </f:entry> <f:advanced> <f:entry title="${%dependencytrack.url.frontend}" field="dependencyTrackFrontendUrl" help="/plugin/dependency-track/help-dt-url-frontend.html"> - <f:textbox id="dependencytrack.url.frontend"/> + <f:textbox id="dependencytrack.url.frontend" checkMethod="post" /> </f:entry> <f:entry title="${%dependencytrack.polling.timeout}" field="dependencyTrackPollingTimeout" help="/plugin/dependency-track/help-dt-polling-timeout.html"> <f:number id="dependencytrack.polling.timeout" default="5" clazz="positive-number" />
src/test/java/org/jenkinsci/plugins/DependencyTrack/DescriptorImplTest.java+4 −4 modified@@ -162,10 +162,10 @@ public void doTestConnectionTestWithEmptyArgs() throws ApiClientException, IOExc @Test public void doCheckDependencyTrackUrlTest() { - assertThat(uut.doCheckDependencyTrackUrl("http://foo.bar/")).isEqualTo(FormValidation.ok()); - assertThat(uut.doCheckDependencyTrackUrl("http://foo.bar")).isEqualTo(FormValidation.ok()); - assertThat(uut.doCheckDependencyTrackUrl("")).isEqualTo(FormValidation.ok()); - assertThat(uut.doCheckDependencyTrackUrl("foo")) + assertThat(uut.doCheckDependencyTrackUrl("http://foo.bar/", null)).isEqualTo(FormValidation.ok()); + assertThat(uut.doCheckDependencyTrackUrl("http://foo.bar", null)).isEqualTo(FormValidation.ok()); + assertThat(uut.doCheckDependencyTrackUrl("", null)).isEqualTo(FormValidation.ok()); + assertThat(uut.doCheckDependencyTrackUrl("foo", null)) .hasFieldOrPropertyWithValue("kind", FormValidation.Kind.ERROR) .hasMessage("The specified value is not a valid URL"); }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-xfrw-pcmc-r2p3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-21632ghsaADVISORY
- www.openwall.com/lists/oss-security/2021/03/30/1ghsamailing-listx_refsource_MLISTWEB
- github.com/jenkinsci/dependency-track-plugin/commit/70e7b82ad9a10499e628998a0bcb57c1481c66bcghsaWEB
- www.jenkins.io/security/advisory/2021-03-30/ghsax_refsource_CONFIRMWEB
News mentions
1- Jenkins Security Advisory 2021-03-30Jenkins Security Advisories · Mar 30, 2021