CVE-2020-2255
Description
Jenkins Blue Ocean Plugin 1.23.2 and earlier allows attackers with Overall/Read permission to connect to an attacker-specified URL.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Jenkins Blue Ocean Plugin 1.23.2 and earlier allows attackers with Overall/Read permission to connect to an attacker-specified URL.
Vulnerability
Description
The Jenkins Blue Ocean Plugin, versions 1.23.2 and earlier, contains a missing permission check in its SCM server endpoint functionality. This flaw allows a user with only the Overall/Read permission to create or modify server connections, which should require higher privileges such as Item/Create or Item/Configure [1][3].
Exploitation
Scenario
An attacker with Overall/Read access can exploit this by sending a crafted request to the /organizations/jenkins/scm/bitbucket-server/servers/ endpoint, specifying an arbitrary URL (e.g., https://attacker-controlled.com/git/). The plugin performs no authorization check before processing the request, enabling the attacker to direct Jenkins to connect to a server under their control [2][3].
Impact
By controlling the target server, the attacker could intercept or manipulate data exchanged between Jenkins and the fake SCM server. This might lead to credential theft, code injection, or further compromise of the Jenkins environment, depending on the subsequent operations performed by the plugin [1][4].
Mitigation
The vulnerability is fixed in Blue Ocean Plugin version 1.23.3, released on 2020-09-16 [1][2]. Users are strongly advised to update to this version. There is no known workaround; restricting Overall/Read permission may reduce exposure but does not fully eliminate the risk for authorized users [4].
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 |
|---|---|---|
io.jenkins.blueocean:blueoceanMaven | < 1.23.3 | 1.23.3 |
Affected products
2- Range: unspecified
Patches
1659a66aff0d0[SECURITY-1961] add permission check for creating servers
9 files changed · +247 −15
acceptance-tests/pom.xml+4 −2 modified@@ -12,6 +12,8 @@ <maven.compiler.target>1.8</maven.compiler.target> <phantomjs.binary.path>./target/phantomjs-maven-plugin/phantomjs-2.1.1-linux-x86_64/bin/phantomjs</phantomjs.binary.path> <argLine>-Djdk.net.URLClassPath.disableClassPathURLCheck=true</argLine> + <slf4j.version>1.7.25</slf4j.version> + <selenium.version>3.141.59</selenium.version> <!-- 3.141.59 4.0.0-alpha-6 --> </properties> @@ -45,12 +47,12 @@ <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> - <version>3.141.59</version> + <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> - <version>3.141.59</version> + <version>${selenium.version}</version> </dependency> <!-- begin HttpRequest helper --> <dependency>
blueocean-bitbucket-pipeline/pom.xml+6 −0 modified@@ -70,6 +70,12 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.jenkins-ci.plugins</groupId> + <artifactId>matrix-auth</artifactId> + <scope>test</scope> + </dependency> + <!-- begin HttpRequest helper --> <dependency> <groupId>io.jenkins.blueocean</groupId>
blueocean-bitbucket-pipeline/src/main/java/io/jenkins/blueocean/blueocean_bitbucket_pipeline/server/BitbucketServerEndpointContainer.java+12 −2 modified@@ -4,7 +4,8 @@ import com.cloudbees.jenkins.plugins.bitbucket.endpoints.BitbucketEndpointConfiguration; import com.google.common.base.Function; import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; +import hudson.model.Item; +import hudson.model.User; import hudson.security.ACL; import io.jenkins.blueocean.blueocean_bitbucket_pipeline.Messages; import io.jenkins.blueocean.commons.ErrorMessage; @@ -13,6 +14,7 @@ import io.jenkins.blueocean.rest.hal.Link; import io.jenkins.blueocean.rest.impl.pipeline.scm.ScmServerEndpoint; import io.jenkins.blueocean.rest.impl.pipeline.scm.ScmServerEndpointContainer; +import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; @@ -22,6 +24,7 @@ import org.slf4j.LoggerFactory; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; /** @@ -42,7 +45,14 @@ public Link getLink() { @Override public ScmServerEndpoint create(JSONObject request) { - List<ErrorMessage.Error> errors = Lists.newLinkedList(); + + try { + Jenkins.get().checkPermission(Item.CREATE); + } catch (Exception e) { + throw new ServiceException.ForbiddenException("User does not have permission to create repository", e); + } + + List<ErrorMessage.Error> errors = new LinkedList<>(); // Validate name final String name = (String) request.get(ScmServerEndpoint.NAME);
blueocean-bitbucket-pipeline/src/test/java/io/jenkins/blueocean/blueocean_bitbucket_pipeline/server/BitbucketServerEndpointSecuredTest.java+96 −0 added@@ -0,0 +1,96 @@ +package io.jenkins.blueocean.blueocean_bitbucket_pipeline.server; + +import com.google.common.collect.ImmutableMap; +import com.mashape.unirest.http.HttpResponse; +import hudson.model.Item; +import hudson.model.User; +import hudson.security.FullControlOnceLoggedInAuthorizationStrategy; +import hudson.security.GlobalMatrixAuthorizationStrategy; +import hudson.security.HudsonPrivateSecurityRealm; +import io.jenkins.blueocean.rest.impl.pipeline.PipelineBaseTest; +import io.jenkins.blueocean.rest.impl.pipeline.scm.ScmServerEndpoint; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Vivek Pandey + */ +public class BitbucketServerEndpointSecuredTest + extends PipelineBaseTest { + private static final String URL = "/organizations/jenkins/scm/bitbucket-server/servers/"; + User readUser, writeUser; + + @Before + public void setupSecurity() throws Exception { + HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(true); + readUser = realm.createAccount("read_user", "pacific_ale"); + writeUser = realm.createAccount("write_user", "pale_ale"); + j.jenkins.setSecurityRealm(realm); + GlobalMatrixAuthorizationStrategy as = new GlobalMatrixAuthorizationStrategy(); + j.jenkins.setAuthorizationStrategy(as); + + as.add( Jenkins.READ, (String)Jenkins.ANONYMOUS.getPrincipal()); + + { + as.add(Jenkins.READ, readUser.getId()); + } + { + as.add( Item.BUILD, writeUser.getId()); + as.add(Item.CREATE, writeUser.getId()); + as.add(Item.CONFIGURE, writeUser.getId()); + } + this.crumb = getCrumb(j.jenkins ); + } + + @Test + public void createAndListFailAnonymous() throws Exception { + HttpResponse<String> response = request() + .crumb( crumb ) + .data(ImmutableMap.of( + "name", "My Server", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(403, response.getStatus()); + } + + @Test + public void createPermissionFail() throws Exception { + + HttpResponse<String> response = request() + .crumb( crumb ) + .jwtToken( getJwtToken( j.jenkins, readUser.getId(), "pacific_ale") ) + .data(ImmutableMap.of( + "name", "My Server", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(403, response.getStatus()); + assertEquals("Forbidden", response.getStatusText()); + assertTrue(response.getBody().contains( "User does not have permission to create repository")); + } + + @Test + public void createPermissionSuccessButMissingValue() throws Exception { + // we only test we passed authorisation check and get bad request response + HttpResponse<String> response = request() + .crumb( crumb ) + .jwtToken( getJwtToken( j.jenkins, writeUser.getId(), "pale_ale") ) + //.auth("read_user", "readonlymate") + .data(ImmutableMap.of( + "name", "", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(400, response.getStatus()); + assertEquals("Bad Request", response.getStatusText()); + assertTrue(response.getBody().contains(ScmServerEndpoint.NAME + " is required")); + } +}
blueocean-github-pipeline/pom.xml+6 −0 modified@@ -62,6 +62,12 @@ <scope>test</scope> </dependency> + <dependency> + <groupId>org.jenkins-ci.plugins</groupId> + <artifactId>matrix-auth</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project>
blueocean-github-pipeline/src/main/java/io/jenkins/blueocean/blueocean_github_pipeline/GithubServerContainer.java+11 −2 modified@@ -6,15 +6,17 @@ import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.hash.Hashing; +import hudson.model.Item; +import hudson.model.User; import hudson.security.ACL; import io.jenkins.blueocean.commons.ErrorMessage; import io.jenkins.blueocean.commons.ServiceException; import io.jenkins.blueocean.rest.hal.Link; import io.jenkins.blueocean.rest.impl.pipeline.scm.ScmServerEndpoint; import io.jenkins.blueocean.rest.impl.pipeline.scm.ScmServerEndpointContainer; +import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; @@ -35,6 +37,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -55,7 +58,13 @@ public class GithubServerContainer extends ScmServerEndpointContainer { public @CheckForNull ScmServerEndpoint create(@JsonBody JSONObject request) { - List<ErrorMessage.Error> errors = Lists.newLinkedList(); + try { + Jenkins.get().checkPermission(Item.CREATE); + } catch (Exception e) { + throw new ServiceException.ForbiddenException("User does not have permission to create repository.", e); + } + + List<ErrorMessage.Error> errors = new LinkedList(); // Validate name final String name = (String) request.get(GithubServer.NAME);
blueocean-github-pipeline/src/test/java/io/jenkins/blueocean/blueocean_github_pipeline/GithubServerSecuredTest.java+91 −0 added@@ -0,0 +1,91 @@ +package io.jenkins.blueocean.blueocean_github_pipeline; + +import com.google.common.collect.ImmutableMap; +import com.mashape.unirest.http.HttpResponse; +import hudson.model.Item; +import hudson.model.User; +import hudson.security.GlobalMatrixAuthorizationStrategy; +import hudson.security.HudsonPrivateSecurityRealm; +import io.jenkins.blueocean.rest.impl.pipeline.PipelineBaseTest; +import jenkins.model.Jenkins; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class GithubServerSecuredTest + extends PipelineBaseTest { + + private static final String URL = "/organizations/jenkins/scm/github-enterprise/servers/"; + User readUser, writeUser; + + @Before + public void setupSecurity() throws Exception { + HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(true); + readUser = realm.createAccount("read_user", "pacific_ale"); + writeUser = realm.createAccount("write_user", "pale_ale"); + j.jenkins.setSecurityRealm(realm); + GlobalMatrixAuthorizationStrategy as = new GlobalMatrixAuthorizationStrategy(); + j.jenkins.setAuthorizationStrategy(as); + + as.add(Jenkins.READ, (String)Jenkins.ANONYMOUS.getPrincipal()); + + { + as.add(Jenkins.READ, readUser.getId()); + } + { + as.add(Item.BUILD, writeUser.getId()); + as.add(Item.CREATE, writeUser.getId()); + as.add(Item.CONFIGURE, writeUser.getId()); + } + this.crumb = getCrumb(j.jenkins ); + } + + @Test + public void createAndListFailAnonymous() throws Exception { + HttpResponse<String> response = request() + .crumb( crumb ) + .data(ImmutableMap.of( + "name", "My Server", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(403, response.getStatus()); + } + + @Test + public void createPermissionFail() throws Exception { + + HttpResponse<String> response = request() + .crumb( crumb ) + .jwtToken( getJwtToken( j.jenkins, readUser.getId(), "pacific_ale") ) + .data(ImmutableMap.of( + "name", "My Server", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(403, response.getStatus()); + assertEquals("Forbidden", response.getStatusText()); + assertTrue(response.getBody().contains( "User does not have permission to create repository")); + } + + @Test + public void createPermissionSuccessButMissingValue() throws Exception { + // we only test we passed authorisation check and get bad request response + HttpResponse<String> response = request() + .crumb( crumb ) + .jwtToken( getJwtToken( j.jenkins, writeUser.getId(), "pale_ale") ) + .data(ImmutableMap.of( + "name", "", + "apiUrl", "https://foo.com/git/" + )) + .post(URL) + .build().asString(); + assertEquals(400, response.getStatus()); + assertEquals("Bad Request", response.getStatusText()); + assertTrue(response.getBody().contains(GithubServer.NAME + " is required")); + } +}
blueocean-github-pipeline/src/test/java/io/jenkins/blueocean/blueocean_github_pipeline/GithubServerTest.java+20 −8 modified@@ -6,10 +6,13 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.common.hash.Hashing; -import com.mashape.unirest.http.exceptions.UnirestException; -import hudson.Util; +import hudson.model.Item; import hudson.model.User; +import hudson.security.GlobalMatrixAuthorizationStrategy; +import hudson.security.HudsonPrivateSecurityRealm; +import hudson.security.Permission; import io.jenkins.blueocean.rest.impl.pipeline.PipelineBaseTest; +import jenkins.model.Jenkins; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -35,10 +38,19 @@ public class GithubServerTest extends PipelineBaseTest { @Before public void createUser() throws Exception { - hudson.model.User user = User.get("alice"); - user.setFullName("Alice Cooper"); - token = getJwtToken(j.jenkins, "alice", "alice"); - this.crumb = getCrumb( j.jenkins ); + HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm( true); + User writeUser = realm.createAccount("write_user", "pale_ale"); + j.jenkins.setSecurityRealm(realm); + GlobalMatrixAuthorizationStrategy as = new GlobalMatrixAuthorizationStrategy(); + j.jenkins.setAuthorizationStrategy(as); + as.add( Jenkins.READ, (String)Jenkins.ANONYMOUS.getPrincipal()); + { + as.add( Item.BUILD, writeUser.getId()); + as.add(Item.CREATE, writeUser.getId()); + as.add(Item.CONFIGURE, writeUser.getId()); + } + token = getJwtToken(j.jenkins, "write_user", "pale_ale"); + this.crumb = getCrumb(j.jenkins); } @Test @@ -47,7 +59,7 @@ public void testServerNotGithub() throws Exception { Map resp = request() .status(400) .jwtToken(token) - .crumb( crumb ) + .crumb(crumb) .data(ImmutableMap.of( "name", "My Server", "apiUrl", getApiUrlCustomPath("/notgithub") @@ -209,7 +221,7 @@ public void avoidDuplicateByUrl() throws Exception { @Test public void avoidDuplicateByName() throws Exception { // Create a server - Map server = request() + request() .status(200) .jwtToken(token) .crumb( crumb )
blueocean-pipeline-api-impl/src/test/java/io/jenkins/blueocean/rest/impl/pipeline/PipelineBaseTest.java+1 −1 modified@@ -92,7 +92,7 @@ public void setup() throws Exception { } this.baseUrl = j.jenkins.getRootUrl() + getContextPath(); this.jwtToken = getJwtToken(j.jenkins); - this.crumb = getCrumb( j.jenkins ); + this.crumb = getCrumb(j.jenkins ); Unirest.setObjectMapper(new ObjectMapper() { public <T> T readValue(String value, Class<T> valueType) {
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-vc7g-4269-f7hwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-2255ghsaADVISORY
- www.openwall.com/lists/oss-security/2020/09/16/3ghsamailing-listx_refsource_MLISTWEB
- github.com/jenkinsci/blueocean-plugin/commit/659a66aff0d0ad693eab9d2807985d591e102aabghsaWEB
- www.jenkins.io/security/advisory/2020-09-16/ghsax_refsource_CONFIRMWEB
News mentions
1- Jenkins Security Advisory 2020-09-16Jenkins Security Advisories · Sep 16, 2020