I recently published a simple POC of CVE-2020-11978 which, when combined with CVE-2020–13927, is an unauthenticated RCE for Apache Airflow 1.10.10. (Exploit DB link)

The exploit is actually simple but when I first encountered CVE-2020–11978, I did some quick google searches and didn’t find any available exploits. 

I’ve always been a user of publicly available exploits when doing CTFs and boxes in hackthebox. So this time I decided to try to look into and write my own. This blog post describes that process .

Finding the CVE 

I encountered this when playing around with snyk and dependabot for some of my personal projects.

What caught my eye was was the HIGH severity RCE (CVE-2020–11978). This was a MEDIUM SEVERITY in dependabot’s output.

And the MEDIUM insecure default (CVE-2020–13927) vulnerability. This was a CRITICAL SEVERITY in dependabot’s output. 

Details of the CVEs

As mentioned the RCE is present in the example DAG load_examples = True and the Experimental API allows unauthenticated by default.

We validate that the default config of airflow is vulnerable to both CVEs in 1.10.10’s default_airflow.cfg here and here

load_examples = True  
auth_backend = airflow.api.auth.backend.default

With the two vulnerabilties we can devlop an exploit that uses the vulnerability in the example DAGs shipped with Airflow and trigger this remotely using the open backend API. By default, you are able to remotely trigger this because the back-end API of Airflow allows unauthenticated requests even if the authentication in the web UI is configured.

To find the vulnerable DAG, we can read through each example DAG. But because these were patched in 1.10.11, we can limit our search to example DAGs that had changes between 1.10.10 and 1.10.11. You can do this is github by using: https://github.com/apache/airflow/compare/1.10.10…1.10.11

We see the following change to the example_trigger_target_dag.py

And we see an update to the docs related to this 

This practically confirms that example_trigger_target_dag.py is the example DAG that CVE-2020–11978 refers to.

Testing and writing the exploit

With this it was straightforward to test and write the exploit. First I tested this with Airflow’s web UI.

The main steps were:

  1. Enable the example_trigger_target_dag 
  2. Create a DAG run with the config above
  3. Wait
  4. Validate the /tmp/test_rce was created

Then I replicated the steps needed to do this using Airflow Experimental API and tested it using docker to make it repeatable (https://github.com/pberba/CVE-2020-11978/blob/main/docker-compose.yml)

In terms of structuring the repository and the details to put about the POC, I learned from the works of other repos. The one I liked was https://github.com/RhinoSecurityLabs/CVEs

Submission to Exploit DB

Finally, I decided to try to submit it to exploit DB. I just followed https://www.exploit-db.com/submit

I just needed to add some header comments on the exploit code. I waited and they accepted it. 

Final Thoughts

Of course, writing exploits with CVEs is a lot easier than actually discovering the CVEs. In this particular case, the actual vulnerability was very easy to find and exploit. Nonetheless, going through this process was very instructive for me.

Repo: https://github.com/pberba/CVE-2020-11978
Exploit DB: https://www.exploit-db.com/exploits/49927

Notes on remediation

The relevant advisory links are:

Remove Example DAGs

If you already have examples disabled by setting load_examples=False in the config then you are not vulnerable.

You can update to >=1.10.11 or remove the vulnerable DAG is example_trigger_target_dag for <1.10.11

Deny access to experimental API

If you start a new Airflow instance using >=1.10.11 , then deny_all is already set for auth_backend by default in airflow.cfg.

auth_backend = airflow.api.auth.backend.deny_all

Note that airflow.api.auth.backend.default still allows unauthenticated requests to the API even for >=1.10.11. So if you have an existing Airflow instance which auth_backend = airflow.api.auth.backend.default then even after upgrading to >=1.10.11, then the REST API is still public.

For >=2.0.0, the experimental API is disabled but has a more powerful stable API.

Photo by Cassie Boca on Unsplash