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

CVE-2020-2255

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.

PackageAffected versionsPatched versions
io.jenkins.blueocean:blueoceanMaven
< 1.23.31.23.3

Affected products

2

Patches

1
659a66aff0d0

[SECURITY-1961] add permission check for creating servers

https://github.com/jenkinsci/blueocean-pluginolivier lamyAug 21, 2020via ghsa
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

News mentions

1