CVE-2020-2202
Description
Jenkins Fortify on Demand Plugin ≤6.0.0 lacks permission checks, allowing Overall/Read users to enumerate stored credential IDs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins Fortify on Demand Plugin ≤6.0.0 lacks permission checks, allowing Overall/Read users to enumerate stored credential IDs.
Vulnerability
Overview
The Jenkins Fortify on Demand Plugin (also known as fortify-on-demand-uploader) version 6.0.0 and earlier contains a missing permission check in multiple form-related methods that provide dropdown lists of credentials [1][2][4]. Specifically, methods such as doFillClientIdItems, doFillClientSecretItems, doFillUsernameItems, doFillPersonalAccessTokenItems, and doFillTenantIdItems previously called SharedUploadBuildStep.doFillStringCredentialsItems() without first verifying that the user had the necessary permissions [1]. The fix introduced in version 6.0.1 requires the Jenkins.ADMINISTER permission before populating these credential lists [1][2].
Attack
Surface and Exploitation
An attacker with only Overall/Read access (the most basic permission in Jenkins) can exploit this flaw by accessing any of the affected form methods via a GET or POST request to the plugin's configuration interface [2][4]. No special privileges beyond read access are required. The vulnerability enables the attacker to obtain a list of credential IDs that correspond to stored credentials in Jenkins—for example, credentials used for Fortify on Demand authentication, such as personal access tokens or client secrets [1][2].
Impact
While the vulnerability only exposes the credential *IDs* (not the secret values themselves), these IDs can be leveraged in a secondary attack to capture the actual credential secrets. As noted in the Jenkins Security Advisory, this enumeration is “part of an attack to capture the credentials using another vulnerability” [2]. The exposure increases the risk of credential theft and unauthorized access to systems protected by those credentials.
Mitigation
Users should upgrade to Fortify on Demand Plugin version 6.0.1 or later, which corrects the permission check by requiring the ADMINISTER permission before listing credentials [1][2][3]. No workarounds are documented; updating the plugin is the recommended action. This issue is distinct from and related to other vulnerabilities in the same plugin (CVE-2020-2203 and CVE-2020-2204) that involve CSRF and additional missing permission checks [2].
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:fortify-on-demand-uploaderMaven | < 6.0.1 | 6.0.1 |
Affected products
2- Range: unspecified
Patches
128932f7c5ff1SECURITY-1690 and SECURITY-1691 fixes
7 files changed · +100 −47
src/main/java/org/jenkinsci/plugins/fodupload/FodGlobalDescriptor.java+27 −5 modified@@ -1,6 +1,11 @@ package org.jenkinsci.plugins.fodupload; +import com.cloudbees.plugins.credentials.CredentialsProvider; import hudson.Extension; +import hudson.model.Item; +import hudson.model.Job; +import hudson.security.ACL; +import hudson.security.Permission; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.util.Secret; @@ -13,6 +18,7 @@ import jenkins.model.Jenkins; import org.jenkinsci.plugins.fodupload.models.FodEnums.GrantType; +import org.jenkinsci.plugins.plaincredentials.StringCredentials; import org.kohsuke.stapler.verb.POST; @Extension @@ -189,27 +195,32 @@ public FormValidation doTestPersonalAccessTokenConnection(@QueryParameter(USERNA @SuppressWarnings("unused") public ListBoxModel doFillClientIdItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + return doFillStringCredentialsItems(); } @SuppressWarnings("unused") public ListBoxModel doFillClientSecretItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + return doFillStringCredentialsItems(); } @SuppressWarnings("unused") public ListBoxModel doFillUsernameItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + return doFillStringCredentialsItems(); } @SuppressWarnings("unused") public ListBoxModel doFillPersonalAccessTokenItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + return doFillStringCredentialsItems(); } @SuppressWarnings("unused") public ListBoxModel doFillTenantIdItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + return doFillStringCredentialsItems(); } FodApiConnection createFodApiConnection() { @@ -262,6 +273,17 @@ public FormValidation testConnection(FodApiConnection testApi) { FormValidation.ok("Successfully authenticated to Fortify on Demand.") : FormValidation.error("Invalid connection information. Please check your credentials and try again."); } + + private ListBoxModel doFillStringCredentialsItems(){ + ListBoxModel items = CredentialsProvider.listCredentials( + StringCredentials.class, + Jenkins.get(), + ACL.SYSTEM, + null, + null + ); + return items; + } }
src/main/java/org/jenkinsci/plugins/fodupload/PollingBuildStep.java+13 −9 modified@@ -7,6 +7,8 @@ import hudson.FilePath; import hudson.Launcher; import hudson.model.AbstractProject; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; @@ -35,6 +37,7 @@ import org.kohsuke.stapler.verb.POST; import static org.jenkinsci.plugins.fodupload.SharedPollingBuildStep.*; +import org.kohsuke.stapler.AncestorInPath; @SuppressWarnings("unused") public class PollingBuildStep extends Recorder implements SimpleBuildStep { @@ -160,9 +163,10 @@ public FormValidation doCheckPollingInterval(@QueryParameter String pollingInter @POST public FormValidation doTestPersonalAccessTokenConnection(@QueryParameter(USERNAME) final String username, @QueryParameter(PERSONAL_ACCESS_TOKEN) final String personalAccessToken, - @QueryParameter(TENANT_ID) final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); - return SharedPollingBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId); + @QueryParameter(TENANT_ID) final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); + return SharedPollingBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId, job); } @SuppressWarnings("unused") @@ -171,18 +175,18 @@ public ListBoxModel doFillPolicyFailureBuildResultPreferenceItems() { } @SuppressWarnings("unused") - public ListBoxModel doFillUsernameItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillUsernameItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillPersonalAccessTokenItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillPersonalAccessTokenItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillTenantIdItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillTenantIdItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } } }
src/main/java/org/jenkinsci/plugins/fodupload/SharedPollingBuildStep.java+10 −3 modified@@ -17,6 +17,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; import hudson.Launcher; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; @@ -25,6 +27,8 @@ import hudson.util.ListBoxModel; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; +import org.kohsuke.stapler.AncestorInPath; + import org.kohsuke.stapler.verb.POST; public class SharedPollingBuildStep { @@ -123,8 +127,10 @@ public static FormValidation doCheckPollingInterval(String pollingInterval) { @POST public static FormValidation doTestPersonalAccessTokenConnection(final String username, final String personalAccessToken, - final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); + final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); + FodApiConnection testApi; String baseUrl = GlobalConfiguration.all().get(FodGlobalDescriptor.class).getBaseUrl(); String apiUrl = GlobalConfiguration.all().get(FodGlobalDescriptor.class).getApiUrl(); @@ -154,7 +160,8 @@ public static ListBoxModel doFillPolicyFailureBuildResultPreferenceItems() { } @SuppressWarnings("unused") - public static ListBoxModel doFillStringCredentialsItems() { + public static ListBoxModel doFillStringCredentialsItems(@AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); ListBoxModel items = CredentialsProvider.listCredentials( StringCredentials.class, Jenkins.get(),
src/main/java/org/jenkinsci/plugins/fodupload/SharedUploadBuildStep.java+8 −3 modified@@ -21,6 +21,8 @@ import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.BuildListener; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; @@ -29,6 +31,7 @@ import hudson.util.ListBoxModel; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.verb.POST; public class SharedUploadBuildStep { @@ -113,8 +116,9 @@ public static FormValidation doCheckBsiToken(String bsiToken, String releaseId) @POST public static FormValidation doTestPersonalAccessTokenConnection(final String username, final String personalAccessToken, - final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); + final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); FodApiConnection testApi; String baseUrl = GlobalConfiguration.all().get(FodGlobalDescriptor.class).getBaseUrl(); String apiUrl = GlobalConfiguration.all().get(FodGlobalDescriptor.class).getApiUrl(); @@ -154,7 +158,8 @@ public static ListBoxModel doFillRemediationScanPreferenceTypeItems() { } @SuppressWarnings("unused") - public static ListBoxModel doFillStringCredentialsItems() { + public static ListBoxModel doFillStringCredentialsItems(@AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); ListBoxModel items = CredentialsProvider.listCredentials( StringCredentials.class, Jenkins.get(),
src/main/java/org/jenkinsci/plugins/fodupload/StaticAssessmentBuildStep.java+13 −9 modified@@ -7,6 +7,8 @@ import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; import hudson.security.Permission; @@ -31,6 +33,7 @@ import jenkins.model.Jenkins; import org.jenkinsci.plugins.fodupload.models.AuthenticationModel; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; @@ -196,9 +199,10 @@ public String getDisplayName() { @POST public FormValidation doTestPersonalAccessTokenConnection(@QueryParameter(SharedUploadBuildStep.USERNAME) final String username, @QueryParameter(SharedUploadBuildStep.PERSONAL_ACCESS_TOKEN) final String personalAccessToken, - @QueryParameter(SharedUploadBuildStep.TENANT_ID) final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); - return SharedUploadBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId); + @QueryParameter(SharedUploadBuildStep.TENANT_ID) final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); + return SharedUploadBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId,job); } @SuppressWarnings("unused") @@ -213,18 +217,18 @@ public ListBoxModel doFillRemediationScanPreferenceTypeItems() { @SuppressWarnings("unused") - public ListBoxModel doFillUsernameItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillUsernameItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillPersonalAccessTokenItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillPersonalAccessTokenItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillTenantIdItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillTenantIdItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused")
src/main/java/org/jenkinsci/plugins/fodupload/steps/FortifyPollResults.java+15 −9 modified@@ -22,11 +22,15 @@ import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.BuildListener; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.util.Secret; +import org.kohsuke.stapler.AncestorInPath; + import org.kohsuke.stapler.verb.POST; @SuppressFBWarnings("unused") @@ -193,9 +197,11 @@ public Set<? extends Class<?>> getRequiredContext() { @POST public FormValidation doTestPersonalAccessTokenConnection(@QueryParameter(SharedPollingBuildStep.USERNAME) final String username, @QueryParameter(SharedPollingBuildStep.PERSONAL_ACCESS_TOKEN) final String personalAccessToken, - @QueryParameter(SharedPollingBuildStep.TENANT_ID) final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); - return SharedPollingBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId); + @QueryParameter(SharedPollingBuildStep.TENANT_ID) final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); + return SharedPollingBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId, job); + } @@ -205,18 +211,18 @@ public ListBoxModel doFillPolicyFailureBuildResultPreferenceItems() { } @SuppressWarnings("unused") - public ListBoxModel doFillUsernameItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillUsernameItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillPersonalAccessTokenItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillPersonalAccessTokenItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillTenantIdItems() { - return SharedPollingBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillTenantIdItems(@AncestorInPath Job job) { + return SharedPollingBuildStep.doFillStringCredentialsItems(job); } }
src/main/java/org/jenkinsci/plugins/fodupload/steps/FortifyStaticAssessment.java+14 −9 modified@@ -21,11 +21,14 @@ import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.BuildListener; +import hudson.model.Item; +import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.util.Secret; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.verb.POST; @@ -211,9 +214,11 @@ public Set<? extends Class<?>> getRequiredContext() { @POST public FormValidation doTestPersonalAccessTokenConnection(@QueryParameter(SharedUploadBuildStep.USERNAME) final String username, @QueryParameter(SharedUploadBuildStep.PERSONAL_ACCESS_TOKEN) final String personalAccessToken, - @QueryParameter(SharedUploadBuildStep.TENANT_ID) final String tenantId) { - Jenkins.get().checkPermission(Jenkins.ADMINISTER); - return SharedUploadBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId); + @QueryParameter(SharedUploadBuildStep.TENANT_ID) final String tenantId, + @AncestorInPath Job job) { + job.checkPermission(Item.CONFIGURE); + return SharedUploadBuildStep.doTestPersonalAccessTokenConnection(username, personalAccessToken, tenantId, job); + } @@ -228,18 +233,18 @@ public ListBoxModel doFillRemediationScanPreferenceTypeItems() { } @SuppressWarnings("unused") - public ListBoxModel doFillUsernameItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillUsernameItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillPersonalAccessTokenItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillPersonalAccessTokenItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused") - public ListBoxModel doFillTenantIdItems() { - return SharedUploadBuildStep.doFillStringCredentialsItems(); + public ListBoxModel doFillTenantIdItems(@AncestorInPath Job job) { + return SharedUploadBuildStep.doFillStringCredentialsItems(job); } @SuppressWarnings("unused")
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-fph2-fwjq-prjfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-2202ghsaADVISORY
- www.openwall.com/lists/oss-security/2020/07/02/7ghsamailing-listx_refsource_MLISTWEB
- github.com/jenkinsci/fortify-on-demand-uploader-plugin/commit/28932f7c5ff18f87d4b3a480225fb0827591776bghsaWEB
- jenkins.io/security/advisory/2020-07-02/ghsax_refsource_CONFIRMWEB
News mentions
1- Jenkins Security Advisory 2020-07-02Jenkins Security Advisories · Jul 2, 2020