CVE-2020-2203
Description
A CSRF vulnerability in Jenkins Fortify on Demand Plugin 5.0.1 and earlier allows attackers to connect to the Fortify endpoint using attacker-specified credentials IDs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A CSRF vulnerability in Jenkins Fortify on Demand Plugin 5.0.1 and earlier allows attackers to connect to the Fortify endpoint using attacker-specified credentials IDs.
Vulnerability
Description
Jenkins Fortify on Demand Plugin 5.0.1 and earlier does not require a valid CSRF token for form submissions that change credential configurations. This allows an attacker to craft a cross-site request forgery (CSRF) attack, tricking an authenticated Jenkins user into unknowingly executing actions on the Fortify endpoint configuration page [2].
Exploitation
The attacker can exploit the CSRF vulnerability by luring an authenticated user (with sufficient permissions) to visit a malicious page while being logged into Jenkins. The crafted request then connects to the globally configured Fortify on Demand endpoint using attacker-specified credentials IDs [1][4]. No additional authentication bypass is needed beyond the existing user session.
Impact
Successful exploitation enables the attacker to use arbitrary Fortify on Demand credentials, potentially leading to unauthorized access to Fortify services, data exfiltration, or further compromise of the Jenkins instance. The plugin also lacked permission checks, allowing users with Overall/Read access to enumerate valid credential IDs [2].
Mitigation
The vulnerability is fixed in Fortify on Demand Plugin 6.0.1, where proper CSRF tokens and permission checks are enforced [1][3]. Users should upgrade immediately. No workarounds are documented.
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.0 | 6.0.0 |
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-p364-xfp2-f9rrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-2203ghsaADVISORY
- 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