CVE-2023-25767
Description
CSRF in Jenkins Azure Credentials Plugin lets attackers trick authenticated users into making Jenkins connect to attacker-controlled servers, potentially leaking credentials.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CSRF in Jenkins Azure Credentials Plugin lets attackers trick authenticated users into making Jenkins connect to attacker-controlled servers, potentially leaking credentials.
Vulnerability
The Azure Credentials Plugin for Jenkins versions 253.v887e0f9e898b and earlier lacks CSRF protection on certain HTTP endpoints. This allows an attacker to perform cross-site request forgery (CSRF) attacks [1][2].
Exploitation
An attacker can trick an authenticated Jenkins user into visiting a malicious page or clicking a crafted link. This triggers a request to the vulnerable endpoint, causing the Jenkins server to connect to an attacker-specified web server [1]. The attack requires the user to be logged in to Jenkins.
Impact
If successful, the attacker can make the Jenkins instance initiate an outbound connection to a server under the attacker's control. This could be used to exfiltrate Azure credentials or other sensitive information [1][2].
Mitigation
The vulnerability is fixed in version 254.v64da_8176c83a of the plugin [1][4]. Users should upgrade immediately.
AI Insight generated on May 20, 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:azure-credentialsMaven | < 254.v64da_8176c83a | 254.v64da_8176c83a |
Affected products
2- Range: unspecified
Patches
164da8176c83a[SECURITY-1756][SECURITY-1757]
13 files changed · +285 −33
src/main/java/com/microsoft/azure/util/AzureCredentials.java+50 −10 modified@@ -15,6 +15,7 @@ import com.azure.resourcemanager.resources.models.Subscription; import com.azure.security.keyvault.secrets.SecretClient; import com.azure.security.keyvault.secrets.SecretClientBuilder; +import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; @@ -35,11 +36,13 @@ import io.jenkins.plugins.azuresdk.HttpClientRetriever; import java.util.List; import jenkins.model.Jenkins; +import org.acegisecurity.Authentication; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.verb.POST; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.ByteArrayInputStream; @@ -153,13 +156,15 @@ CertificateCredentialsImpl getCertificate() { if (StringUtils.isEmpty(certificateId)) { return null; } - return CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials( - CertificateCredentialsImpl.class, - Jenkins.get(), - ACL.SYSTEM, - Collections.emptyList()), - CredentialsMatchers.withId(certificateId)); + CertificateCredentialsImpl certificate = getCredentials( + CertificateCredentialsImpl.class, + certificateId, ACL.SYSTEM); + if (certificate == null) { + return getCredentials( + CertificateCredentialsImpl.class, + certificateId, Jenkins.getAuthentication()); + } + return null; } @Nullable @@ -565,6 +570,17 @@ public static TokenCredential getSystemCredentialById(String credentialID) { return credential; } + private static <T extends Credentials> T getCredentials( + Class<T> type, String certificateId, Authentication authentication) { + return CredentialsMatchers.firstOrNull( + CredentialsProvider.lookupCredentials( + type, + Jenkins.get(), + authentication, + Collections.emptyList()), + CredentialsMatchers.withId(certificateId)); + } + public static TokenCredential getTokenCredential(AzureBaseCredentials credentials) { if (credentials instanceof AzureCredentials) { @@ -769,7 +785,10 @@ public String getDisplayName() { return "Azure Service Principal"; } + + @POST public FormValidation doVerifyConfiguration( + @AncestorInPath Item owner, @QueryParameter String subscriptionId, @QueryParameter String clientId, @QueryParameter String clientSecret, @@ -780,6 +799,11 @@ public FormValidation doVerifyConfiguration( @QueryParameter String authenticationEndpoint, @QueryParameter String resourceManagerEndpoint, @QueryParameter String graphEndpoint) { + if (owner == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + owner.checkPermission(Item.CONFIGURE); + } AzureCredentials.ServicePrincipal servicePrincipal = new AzureCredentials.ServicePrincipal(subscriptionId, clientId, Secret.fromString(clientSecret)); @@ -799,11 +823,27 @@ public FormValidation doVerifyConfiguration( return FormValidation.ok(Messages.Azure_Config_Success()); } - public ListBoxModel doFillCertificateIdItems(@AncestorInPath Item owner) { + public ListBoxModel doFillCertificateIdItems( + @AncestorInPath Item owner, + @QueryParameter("certificateId") String certificateId) { StandardListBoxModel model = new StandardListBoxModel(); model.add(Messages.Azure_Credentials_Select(), ""); - model.includeAs(ACL.SYSTEM, owner, CertificateCredentialsImpl.class); - return model; + if (owner == null) { + if (!Jenkins.get().hasPermission(CredentialsProvider.CREATE) + && !Jenkins.get().hasPermission(CredentialsProvider.UPDATE)) { + return model.includeCurrentValue(certificateId); + } + } else { + if (!owner.hasPermission(CredentialsProvider.CREATE) + && !owner.hasPermission(CredentialsProvider.UPDATE)) { + return model.includeCurrentValue(certificateId); + } + } + + return model + .includeCurrentValue(certificateId) + .includeAs(Jenkins.getAuthentication(), owner, CertificateCredentialsImpl.class) + .includeAs(ACL.SYSTEM, owner, CertificateCredentialsImpl.class); } public ListBoxModel doFillAzureEnvironmentNameItems() {
src/main/java/com/microsoft/azure/util/AzureImdsCredentials.java+20 −0 modified@@ -1,5 +1,7 @@ package com.microsoft.azure.util; +import com.azure.core.http.policy.FixedDelay; +import com.azure.core.http.policy.RetryPolicy; import com.azure.core.http.rest.PagedIterable; import com.azure.core.management.profile.AzureProfile; import com.azure.identity.ManagedIdentityCredentialBuilder; @@ -8,14 +10,20 @@ import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; import hudson.Extension; +import hudson.Main; import hudson.Util; +import hudson.model.Item; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import io.jenkins.plugins.azuresdk.HttpClientRetriever; +import java.time.Duration; +import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.verb.POST; public class AzureImdsCredentials extends AbstractManagedIdentitiesCredentials { @@ -65,6 +73,7 @@ public boolean validate() throws AzureCredentials.ValidationException { AzureResourceManager azure = AzureResourceManager .configure() + .withRetryPolicy(getRetryPolicy()) .withHttpClient(HttpClientRetriever.get()) .authenticate(credentialBuilder.build(), profile) .withSubscription(credentialSubscriptionId); @@ -86,6 +95,10 @@ public boolean validate() throws AzureCredentials.ValidationException { throw new AzureCredentials.ValidationException(Messages.Azure_Invalid_SubscriptionId()); } + private static RetryPolicy getRetryPolicy() { + return Main.isUnitTest ? new RetryPolicy(new FixedDelay(0, Duration.ZERO)) : new RetryPolicy(); + } + @Extension public static class DescriptorImpl @@ -104,10 +117,17 @@ public ListBoxModel doFillAzureEnvNameItems() { return model; } + @POST public FormValidation doVerifyConfiguration( + @AncestorInPath Item owner, @QueryParameter String subscriptionId, @QueryParameter String clientId, @QueryParameter String azureEnvironmentName) { + if (owner == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + owner.checkPermission(Item.CONFIGURE); + } AzureImdsCredentials imdsCredentials = new AzureImdsCredentials(null, null, null, azureEnvironmentName); if (StringUtils.isNotBlank(subscriptionId)) {
src/main/java/com/microsoft/jenkins/keyvault/BaseSecretCredentials.java+25 −4 modified@@ -7,6 +7,7 @@ import com.azure.security.keyvault.secrets.SecretClient; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; +import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; @@ -15,6 +16,10 @@ import hudson.model.Item; import hudson.security.ACL; import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.QueryParameter; + import java.net.MalformedURLException; import java.net.URL; @@ -94,10 +99,26 @@ interface SecretGetter { protected abstract static class DescriptorImpl extends BaseStandardCredentialsDescriptor { - public ListBoxModel doFillServicePrincipalIdItems(Item owner) { - return new StandardListBoxModel() - .includeEmptyValue() - .includeAs(ACL.SYSTEM, owner, AzureCredentials.class); + public ListBoxModel doFillServicePrincipalIdItems( + @AncestorInPath Item owner, + @QueryParameter("servicePrincipalId") String servicePrincipalId) { + StandardListBoxModel model = new StandardListBoxModel(); + model.includeEmptyValue(); + if (owner == null) { + if (!Jenkins.get().hasPermission(CredentialsProvider.CREATE) + && !Jenkins.get().hasPermission(CredentialsProvider.UPDATE)) { + return model.includeCurrentValue(servicePrincipalId); + } + } else { + if (!owner.hasPermission(CredentialsProvider.CREATE) + && !owner.hasPermission(CredentialsProvider.UPDATE)) { + return model.includeCurrentValue(servicePrincipalId); + } + } + return model + .includeCurrentValue(servicePrincipalId) + .includeAs(Jenkins.getAuthentication(), owner, AzureCredentials.class) + .includeAs(ACL.SYSTEM, owner, AzureCredentials.class); } } }
src/main/java/com/microsoft/jenkins/keyvault/SecretCertificateCredentials.java+11 −0 modified@@ -11,11 +11,15 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.Util; +import hudson.model.Item; import hudson.util.FormValidation; import hudson.util.Secret; +import jenkins.model.Jenkins; import org.apache.commons.codec.binary.Base64; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.verb.POST; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -102,10 +106,17 @@ public String getDisplayName() { return Messages.Certificate_Credentials_Display_Name(); } + @POST public FormValidation doVerifyConfiguration( + @AncestorInPath Item owner, @QueryParameter String servicePrincipalId, @QueryParameter String secretIdentifier, @QueryParameter Secret password) { + if (owner == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + owner.checkPermission(Item.CONFIGURE); + } final SecretCertificateCredentials credentials = new SecretCertificateCredentials( CredentialsScope.SYSTEM, "", "", servicePrincipalId, secretIdentifier, password);
src/main/java/com/microsoft/jenkins/keyvault/SecretStringCredentials.java+12 −0 modified@@ -7,11 +7,15 @@ import com.azure.security.keyvault.secrets.models.KeyVaultSecret; import com.cloudbees.plugins.credentials.CredentialsScope; import hudson.Extension; +import hudson.model.Item; import hudson.util.FormValidation; import hudson.util.Secret; +import jenkins.model.Jenkins; import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.verb.POST; import edu.umd.cs.findbugs.annotations.NonNull; @@ -43,9 +47,17 @@ public String getDisplayName() { return Messages.String_Credentials_Diaplay_Name(); } + + @POST public FormValidation doVerifyConfiguration( + @AncestorInPath Item owner, @QueryParameter String servicePrincipalId, @QueryParameter String secretIdentifier) { + if (owner == null) { + Jenkins.get().checkPermission(Jenkins.ADMINISTER); + } else { + owner.checkPermission(Item.CONFIGURE); + } final SecretStringCredentials credentials = new SecretStringCredentials( CredentialsScope.SYSTEM, "", "", servicePrincipalId, secretIdentifier);
src/main/resources/com/microsoft/azure/util/AzureCredentials/credentials.jelly+1 −1 modified@@ -13,7 +13,7 @@ <f:password/> </f:entry> <f:entry title="${%CertificateId}" field="certificateId" help="/plugin/azure-credentials/help-certificateId.html"> - <c:select expressionAllowed="false" /> + <c:select expressionAllowed="false" checkMethod="post" /> </f:entry> <f:entry title="${%Tenant}" field="tenant" help="/plugin/azure-credentials/help-tenant.html"> <f:textbox/>
src/main/resources/com/microsoft/jenkins/keyvault/SecretCertificateCredentials/credentials.jelly+1 −1 modified@@ -6,7 +6,7 @@ <?jelly escape-by-default='true'?> <j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:st="jelly:stapler" xmlns:c="/lib/credentials"> <f:entry title="${%ServicePrincipal}" field="servicePrincipalId"> - <c:select expressionAllowed="false"/> + <c:select expressionAllowed="false" checkMethod="post"/> </f:entry> <f:entry title="${%SecretIdentifier}" field="secretIdentifier">
src/main/resources/com/microsoft/jenkins/keyvault/SecretStringCredentials/credentials.jelly+1 −1 modified@@ -6,7 +6,7 @@ <?jelly escape-by-default='true'?> <j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form" xmlns:st="jelly:stapler" xmlns:c="/lib/credentials"> <f:entry title="${%ServicePrincipal}" field="servicePrincipalId"> - <c:select expressionAllowed="false"/> + <c:select expressionAllowed="false" checkMethod="post" /> </f:entry> <f:entry title="${%SecretIdentifier}" field="secretIdentifier">
src/test/java/com/microsoft/azure/util/AzureImdsCredentialsTest.java+64 −0 added@@ -0,0 +1,64 @@ +package com.microsoft.azure.util; + +import com.cloudbees.hudson.plugins.folder.Folder; +import hudson.model.Item; +import hudson.model.User; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.security.AccessDeniedException3; +import hudson.util.FormValidation; +import jenkins.model.Jenkins; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; + +public class AzureImdsCredentialsTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void descriptorVerifyConfigurationAsAdmin() { + // No security realm, anonymous has Overall/Administer + final AzureImdsCredentials.DescriptorImpl descriptor = new AzureImdsCredentials.DescriptorImpl(); + + FormValidation result = descriptor.doVerifyConfiguration(null,"", "", ""); + Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); + } + + @Test + public void descriptorVerifyConfigurationWithAncestorAsAuthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + authorizationStrategy.grant(Item.CONFIGURE).onFolders(folder).to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final AzureImdsCredentials.DescriptorImpl descriptor = new AzureImdsCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + FormValidation result = descriptor.doVerifyConfiguration(folder, "", "", ""); + // we aren't looking up an actual secret so this fails with missing protocol + // TODO mock secrets retrieval so we can test the happy case here properly + Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); + } + } + + @Test + public void descriptorVerifyConfigurationWithAncestorAsUnauthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final AzureImdsCredentials.DescriptorImpl descriptor = new AzureImdsCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + Assert.assertThrows(AccessDeniedException3.class, () -> descriptor.doVerifyConfiguration(folder, "", "", "")); + } + } +}
src/test/java/com/microsoft/jenkins/keyvault/integration/ITSecretCertificateCredentials.java+3 −4 modified@@ -34,8 +34,7 @@ public void getKeyStore() throws IOException, KeyStoreException, UnrecoverableKe // Verify configuration final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); - final FormValidation result = descriptor.doVerifyConfiguration( - jenkinsAzureCredentialsId, secretIdentifier, password); + final FormValidation result = descriptor.doVerifyConfiguration(null, jenkinsAzureCredentialsId, secretIdentifier, password); Assert.assertEquals(FormValidation.Kind.OK, result.kind); // Get key store @@ -55,7 +54,7 @@ public void getKeyStoreNotFound() { // Verify configuration final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); - final FormValidation result = descriptor.doVerifyConfiguration(jenkinsAzureCredentialsId, + final FormValidation result = descriptor.doVerifyConfiguration(null, jenkinsAzureCredentialsId, secretIdentifier, Secret.fromString("")); Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); @@ -79,7 +78,7 @@ public void getKeyStoreNoPrivateKey() throws IOException { // Verify configuration final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); - final FormValidation result = descriptor.doVerifyConfiguration(jenkinsAzureCredentialsId, + final FormValidation result = descriptor.doVerifyConfiguration(null, jenkinsAzureCredentialsId, secretIdentifier, Secret.fromString("")); Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); Assert.assertEquals(Messages.Certificate_Credentials_Validation_No_Private_Key(), result.getMessage());
src/test/java/com/microsoft/jenkins/keyvault/integration/ITSecretStringCredentials.java+2 −2 modified@@ -22,7 +22,7 @@ public void getSecret() { // Verify configuration final SecretStringCredentials.DescriptorImpl descriptor = new SecretStringCredentials.DescriptorImpl(); - final FormValidation result = descriptor.doVerifyConfiguration(jenkinsAzureCredentialsId, secretIdentifier); + final FormValidation result = descriptor.doVerifyConfiguration(null, jenkinsAzureCredentialsId, secretIdentifier); Assert.assertEquals(FormValidation.Kind.OK, result.kind); // Get secret @@ -39,7 +39,7 @@ public void getSecretNotFound() { // Verify configuration final SecretStringCredentials.DescriptorImpl descriptor = new SecretStringCredentials.DescriptorImpl(); - final FormValidation result = descriptor.doVerifyConfiguration(jenkinsAzureCredentialsId, + final FormValidation result = descriptor.doVerifyConfiguration(null, jenkinsAzureCredentialsId, secretIdentifier); Assert.assertEquals(FormValidation.Kind.ERROR, result.kind);
src/test/java/com/microsoft/jenkins/keyvault/SecretCertificateCredentialsTest.java+47 −5 modified@@ -7,14 +7,22 @@ import com.azure.security.keyvault.secrets.models.KeyVaultSecret; import com.azure.security.keyvault.secrets.models.SecretProperties; +import com.cloudbees.hudson.plugins.folder.Folder; import com.cloudbees.plugins.credentials.CredentialsScope; +import hudson.model.Item; +import hudson.model.User; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.security.AccessDeniedException3; import hudson.util.FormValidation; import hudson.util.Secret; +import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import org.junit.Assert; -import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; import java.io.IOException; import java.security.Key; @@ -25,8 +33,8 @@ public class SecretCertificateCredentialsTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); + @Rule + public JenkinsRule j = new JenkinsRule(); private static class MockCertSecretGetter implements BaseSecretCredentials.SecretGetter { @@ -70,11 +78,45 @@ public void getKeyStore() throws IOException, KeyStoreException, UnrecoverableKe } @Test - public void descriptorVerifyConfiguration() { + public void descriptorVerifyConfigurationAsAdmin() { + // No security realm, anonymous has Overall/Administer final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); - FormValidation result = descriptor.doVerifyConfiguration("", "", Secret.fromString("")); + FormValidation result = descriptor.doVerifyConfiguration(null, "", "", Secret.fromString("")); Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); } + @Test + public void descriptorVerifyConfigurationWithAncestorAsAuthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + authorizationStrategy.grant(Item.CONFIGURE).onFolders(folder).to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + FormValidation result = descriptor.doVerifyConfiguration(folder, "", "", Secret.fromString("")); + // we aren't looking up an actual secret so this fails with missing protocol + // TODO mock secrets retrieval so we can test the happy case here properly + Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); + } + } + + @Test + public void descriptorVerifyConfigurationWithAncestorAsUnauthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final SecretCertificateCredentials.DescriptorImpl descriptor = new SecretCertificateCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + Assert.assertThrows(AccessDeniedException3.class, () -> descriptor.doVerifyConfiguration(folder, "", "", Secret.fromString(""))); + } + } }
src/test/java/com/microsoft/jenkins/keyvault/SecretStringCredentialsTest.java+48 −5 modified@@ -6,18 +6,26 @@ package com.microsoft.jenkins.keyvault; import com.azure.security.keyvault.secrets.models.KeyVaultSecret; +import com.cloudbees.hudson.plugins.folder.Folder; import com.cloudbees.plugins.credentials.CredentialsScope; +import hudson.model.Item; +import hudson.model.User; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.security.AccessDeniedException3; import hudson.util.FormValidation; import hudson.util.Secret; +import jenkins.model.Jenkins; import org.junit.Assert; -import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; public class SecretStringCredentialsTest { - @ClassRule - public static JenkinsRule j = new JenkinsRule(); + @Rule + public JenkinsRule j = new JenkinsRule(); @Test public void getSecret() { @@ -46,11 +54,46 @@ public KeyVaultSecret getKeyVaultSecret(String credentialId, String secretIdenti } @Test - public void descriptorVerifyConfiguration() { + public void descriptorVerifyConfigurationAsAdmin() { + // No security realm, anonymous has Overall/Administer final SecretStringCredentials.DescriptorImpl descriptor = new SecretStringCredentials.DescriptorImpl(); - FormValidation result = descriptor.doVerifyConfiguration("", ""); + FormValidation result = descriptor.doVerifyConfiguration(null,"", ""); Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); } + @Test + public void descriptorVerifyConfigurationWithAncestorAsAuthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + authorizationStrategy.grant(Item.CONFIGURE).onFolders(folder).to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final SecretStringCredentials.DescriptorImpl descriptor = new SecretStringCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + FormValidation result = descriptor.doVerifyConfiguration(folder, "", ""); + // we aren't looking up an actual secret so this fails with missing protocol + // TODO mock secrets retrieval so we can test the happy case here properly + Assert.assertEquals(FormValidation.Kind.ERROR, result.kind); + } + } + + @Test + public void descriptorVerifyConfigurationWithAncestorAsUnauthorizedUser() throws Exception { + Folder folder = j.jenkins.createProject(Folder.class, "folder"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + MockAuthorizationStrategy authorizationStrategy = new MockAuthorizationStrategy(); + authorizationStrategy.grant(Jenkins.READ).everywhere().to("user"); + j.jenkins.setAuthorizationStrategy(authorizationStrategy); + + final SecretStringCredentials.DescriptorImpl descriptor = new SecretStringCredentials.DescriptorImpl(); + + try (ACLContext ctx = ACL.as(User.getOrCreateByIdOrFullName("user"))) { + Assert.assertThrows(AccessDeniedException3.class, () -> descriptor.doVerifyConfiguration(folder, "", "")); + } + } + }
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-rr93-7c6x-8v4vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-25767ghsaADVISORY
- www.openwall.com/lists/oss-security/2023/02/15/4ghsamailing-listWEB
- github.com/jenkinsci/azure-credentials-plugin/commit/64da8176c83a41bb83d3ad759628c9bd275b42f5ghsaWEB
- www.jenkins.io/security/advisory/2023-02-15/ghsaWEB
News mentions
1- Jenkins Security Advisory 2023-02-15Jenkins Security Advisories · Feb 15, 2023