VYPR
Moderate severityNVD Advisory· Published Nov 4, 2020· Updated Aug 4, 2024

CVE-2020-2310

CVE-2020-2310

Description

Missing permission checks in Jenkins Ansible Plugin 1.0 and earlier allow attackers with Overall/Read permission to enumerate credentials IDs.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Missing permission checks in Jenkins Ansible Plugin 1.0 and earlier allow attackers with Overall/Read permission to enumerate credentials IDs.

Vulnerability

Overview

The Jenkins Ansible Plugin versions 1.0 and earlier contain a missing permission check in the credential ID selection method. The doFillCredentialsIdItems method did not verify that the user had the required permissions (such as Item.EXTENDED_READ or CredentialsProvider.USE_ITEM) before returning a list of credential IDs. This flaw allows any user with the low-privilege Overall/Read permission to enumerate the IDs of all credentials stored in Jenkins [1][3].

Exploitation

An attacker with only Overall/Read permission can exploit this vulnerability by directly invoking the credential ID selection endpoint. No additional authentication or special network position is required beyond having a Jenkins account with that minimal permission. The method returns a list of credential IDs that are otherwise not accessible to users without broader permissions [1].

Impact

While credential IDs themselves are not secret values, their enumeration provides attackers with valuable information. Knowing the IDs allows an attacker to target specific credentials in subsequent attacks, such as using them in other plugin forms or attempting to brute-force the corresponding secrets. This information disclosure can be a stepping stone for more severe compromises [1][4].

Mitigation

The issue has been fixed in Ansible Plugin version 1.1 by adding proper permission checks in the doFillCredentialsIdItems method. Users are strongly advised to update to version 1.1 or later. No workarounds are available for earlier versions [1][3].

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.

PackageAffected versionsPatched versions
org.jenkins-ci.plugins:ansibleMaven
< 1.11.1

Affected products

2

Patches

1
503be2bc90f7

[SECURITY-1943]

https://github.com/jenkinsci/ansible-pluginEmilio EscobarOct 30, 2020via ghsa
4 files changed · +126 50
  • pom.xml+2 2 modified
    @@ -145,13 +145,13 @@
     
       <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    -    <jenkins.version>1.596.1</jenkins.version>
    +    <jenkins.version>1.609.1</jenkins.version>
         <maven-compiler-plugin.version>3.2</maven-compiler-plugin.version>
         <maven-resources-plugin.version>2.6</maven-resources-plugin.version>
         <maven-release-plugin.version>2.5.2</maven-release-plugin.version>
         <ssh-credentials.version>1.10</ssh-credentials.version>
         <plain-credentials.version>1.4</plain-credentials.version>
    -    <credentials.version>1.16.1</credentials.version>
    +    <credentials.version>2.1.0</credentials.version>
         <workflow-step-api.version>1.10</workflow-step-api.version>
         <junit.version>4.12</junit.version>
         <mockito.version>1.10.19</mockito.version>
    
  • src/main/java/org/jenkinsci/plugins/ansible/AbstractAnsibleBuilderDescriptor.java+49 20 modified
    @@ -11,6 +11,7 @@
     import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
     import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
     import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
    +import hudson.model.Item;
     import org.jenkinsci.plugins.plaincredentials.FileCredentials;
     import org.jenkinsci.plugins.plaincredentials.StringCredentials;
     import hudson.model.AbstractProject;
    @@ -23,6 +24,7 @@
     import org.apache.commons.lang.StringUtils;
     import org.jenkinsci.plugins.ansible.Inventory.InventoryDescriptor;
     import org.kohsuke.stapler.AncestorInPath;
    +import org.kohsuke.stapler.QueryParameter;
     
     /**
      * Common descriptor for Ansible build steps
    @@ -44,31 +46,58 @@ protected FormValidation checkNotNullOrEmpty(String parameter, String errorMessa
             }
         }
     
    -    public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) {
    -        return new StandardListBoxModel()
    -                .withEmptySelection()
    +    public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item,
    +                                                 @QueryParameter String credentialsId) {
    +
    +        StandardListBoxModel result = new StandardListBoxModel();
    +        if (item == null) {
    +            if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
    +                return result.includeCurrentValue(credentialsId);
    +            }
    +        } else {
    +            if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                return result.includeCurrentValue(credentialsId);
    +            }
    +        }
    +
    +        return result.includeEmptyValue()
                     .withMatching(anyOf(
    -                            instanceOf(SSHUserPrivateKey.class),
    -                            instanceOf(UsernamePasswordCredentials.class)),
    -                        CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project));
    +                        instanceOf(SSHUserPrivateKey.class),
    +                        instanceOf(UsernamePasswordCredentials.class)),
    +                        CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, item))
    +                .includeCurrentValue(credentialsId);
    +    }
    +
    +    public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item,
    +                                                      @QueryParameter String vaultCredentialsId) {
    +        return fillVaultCredentials(item, vaultCredentialsId);
         }
     
    -    public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) {
    -        return new StandardListBoxModel()
    -            .withEmptySelection()
    -            .withMatching(anyOf(
    -                instanceOf(FileCredentials.class),
    -                instanceOf(StringCredentials.class)),
    -                CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
    +    public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Item item,
    +                                                         @QueryParameter String newVaultCredentialsId) {
    +        return fillVaultCredentials(item, newVaultCredentialsId);
         }
     
    -    public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Project project) {
    -        return new StandardListBoxModel()
    -            .withEmptySelection()
    -            .withMatching(anyOf(
    -                instanceOf(FileCredentials.class),
    -                instanceOf(StringCredentials.class)),
    -                CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
    +    private ListBoxModel fillVaultCredentials(Item item, String credentialsId) {
    +        StandardListBoxModel result = new StandardListBoxModel();
    +        if (item == null) {
    +            if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
    +                return result.includeCurrentValue(credentialsId);
    +            }
    +        } else {
    +            if (!item.hasPermission(Item.EXTENDED_READ)
    +                    && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                return result.includeCurrentValue(credentialsId);
    +            }
    +        }
    +
    +        return result.includeEmptyValue()
    +                .withMatching(anyOf(
    +                        instanceOf(FileCredentials.class),
    +                        instanceOf(StringCredentials.class)),
    +                        CredentialsProvider.lookupCredentials(StandardCredentials.class, item))
    +                .includeCurrentValue(credentialsId);
         }
     
         public List<InventoryDescriptor> getInventories() {
    
  • src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsiblePlaybookStep.java+44 14 modified
    @@ -32,11 +32,13 @@
     import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     import hudson.*;
     import hudson.model.Computer;
    +import hudson.model.Item;
     import hudson.model.Node;
     import hudson.model.Project;
     import hudson.model.Run;
     import hudson.model.TaskListener;
     import hudson.util.ListBoxModel;
    +import jenkins.model.Jenkins;
     import org.apache.commons.lang.StringUtils;
     import org.jenkinsci.plugins.ansible.AnsibleInstallation;
     import org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder;
    @@ -53,6 +55,7 @@
     import org.kohsuke.stapler.AncestorInPath;
     import org.kohsuke.stapler.DataBoundConstructor;
     import org.kohsuke.stapler.DataBoundSetter;
    +import org.kohsuke.stapler.QueryParameter;
     
     /**
      * The Ansible playbook invocation step for the Jenkins workflow plugin.
    @@ -291,22 +294,49 @@ public String getDisplayName() {
                 return "Invoke an ansible playbook";
             }
     
    -        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) {
    -            return new StandardListBoxModel()
    -                .withEmptySelection()
    -                .withMatching(anyOf(
    -                    instanceOf(SSHUserPrivateKey.class),
    -                    instanceOf(UsernamePasswordCredentials.class)),
    -                    CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project));
    +        public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item,
    +                                                     @QueryParameter String credentialsId) {
    +
    +            StandardListBoxModel result = new StandardListBoxModel();
    +            if (item == null) {
    +                if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
    +                    return result.includeCurrentValue(credentialsId);
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                        && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return result.includeCurrentValue(credentialsId);
    +                }
    +            }
    +
    +            return result.includeEmptyValue()
    +                    .withMatching(anyOf(
    +                            instanceOf(SSHUserPrivateKey.class),
    +                            instanceOf(UsernamePasswordCredentials.class)),
    +                            CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, item))
    +                    .includeCurrentValue(credentialsId);
             }
     
    -        public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) {
    -            return new StandardListBoxModel()
    -                .withEmptySelection()
    -                .withMatching(anyOf(
    -                    instanceOf(FileCredentials.class),
    -                    instanceOf(StringCredentials.class)),
    -                    CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
    +        public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item,
    +                                                          @QueryParameter String vaultCredentialsId) {
    +            StandardListBoxModel result = new StandardListBoxModel();
    +            if (item == null) {
    +                if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
    +                    return result.includeCurrentValue(vaultCredentialsId);
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                        && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return result.includeCurrentValue(vaultCredentialsId);
    +                }
    +            }
    +
    +            return result.includeEmptyValue()
    +                    .withMatching(anyOf(
    +                            instanceOf(FileCredentials.class),
    +                            instanceOf(StringCredentials.class)),
    +                            CredentialsProvider.lookupCredentials(StandardCredentials.class, item))
    +                    .includeCurrentValue(vaultCredentialsId);
             }
     
             public ListBoxModel doFillInstallationItems() {
    
  • src/main/java/org/jenkinsci/plugins/ansible/workflow/AnsibleVaultStep.java+31 14 modified
    @@ -24,11 +24,13 @@
     import com.google.inject.Inject;
     import hudson.*;
     import hudson.model.Computer;
    +import hudson.model.Item;
     import hudson.model.Node;
     import hudson.model.Project;
     import hudson.model.Run;
     import hudson.model.TaskListener;
     import hudson.util.ListBoxModel;
    +import jenkins.model.Jenkins;
     import org.jenkinsci.plugins.ansible.AnsibleInstallation;
     import org.jenkinsci.plugins.ansible.AnsibleVaultBuilder;
     import org.jenkinsci.plugins.plaincredentials.FileCredentials;
    @@ -40,6 +42,7 @@
     import org.kohsuke.stapler.AncestorInPath;
     import org.kohsuke.stapler.DataBoundConstructor;
     import org.kohsuke.stapler.DataBoundSetter;
    +import org.kohsuke.stapler.QueryParameter;
     
     /**
      * The Ansible vault invocation step for the Jenkins workflow plugin.
    @@ -139,22 +142,14 @@ public String getDisplayName() {
                 return "Invoke ansible vault";
             }
     
    -        public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) {
    -            return new StandardListBoxModel()
    -                .withEmptySelection()
    -                .withMatching(anyOf(
    -                    instanceOf(FileCredentials.class),
    -                    instanceOf(StringCredentials.class)),
    -                    CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
    +        public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Item item,
    +                                                          @QueryParameter String vaultCredentialsId) {
    +            return fillVaultCredentials(item, vaultCredentialsId);
             }
     
    -        public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Project project) {
    -            return new StandardListBoxModel()
    -                .withEmptySelection()
    -                .withMatching(anyOf(
    -                    instanceOf(FileCredentials.class),
    -                    instanceOf(StringCredentials.class)),
    -                    CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
    +        public ListBoxModel doFillNewVaultCredentialsIdItems(@AncestorInPath Item item,
    +                                                             @QueryParameter String newVaultCredentialsId) {
    +            return fillVaultCredentials(item, newVaultCredentialsId);
             }
     
             public ListBoxModel doFillInstallationItems() {
    @@ -164,6 +159,28 @@ public ListBoxModel doFillInstallationItems() {
                 }
                 return model;
             }
    +
    +
    +        private ListBoxModel fillVaultCredentials(Item item, String credentialsId) {
    +            StandardListBoxModel result = new StandardListBoxModel();
    +            if (item == null) {
    +                if (!Jenkins.getActiveInstance().hasPermission(Jenkins.ADMINISTER)) {
    +                    return result.includeCurrentValue(credentialsId);
    +                }
    +            } else {
    +                if (!item.hasPermission(Item.EXTENDED_READ)
    +                        && !item.hasPermission(CredentialsProvider.USE_ITEM)) {
    +                    return result.includeCurrentValue(credentialsId);
    +                }
    +            }
    +
    +            return result.includeEmptyValue()
    +                    .withMatching(anyOf(
    +                            instanceOf(FileCredentials.class),
    +                            instanceOf(StringCredentials.class)),
    +                            CredentialsProvider.lookupCredentials(StandardCredentials.class, item))
    +                    .includeCurrentValue(credentialsId);
    +        }
         }
     
         public static final class AnsibleVaultExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

1