Module: Msf::Exploit::Remote::HTTP::Beyondtrust

Includes:
Msf::Exploit::Remote::HttpClient
Defined in:
lib/msf/core/exploit/remote/http/beyondtrust.rb

Instance Attribute Summary

Attributes included from Msf::Exploit::Remote::HttpClient

#client, #cookie_jar

Instance Method Summary collapse

Methods included from Msf::Exploit::Remote::HttpClient

#basic_auth, #cleanup, #configure_http_login_scanner, #connect, #connect_ws, #deregister_http_client_options, #disconnect, #download, #full_uri, #handler, #http_fingerprint, #lookup_http_fingerprints, #normalize_uri, #path_from_uri, #peer, #proxies, #reconfig_redirect_opts!, #request_opts_from_url, #request_url, #rhost, #rport, #send_request_cgi, #send_request_cgi!, #send_request_raw, #service_details, #setup, #ssl, #ssl_version, #sslkeylogfile, #strip_tags, #target_uri, #validate_fingerprint, #vhost

Methods included from Kerberos::ServiceAuthenticator::Options

#kerberos_auth_options, #kerberos_clock_skew_seconds

Methods included from Kerberos::Ticket::Storage

#kerberos_storage_options, #kerberos_ticket_storage, store_ccache

Methods included from Auxiliary::LoginScanner

#configure_login_scanner

Methods included from Auxiliary::Report

#active_db?, #create_cracked_credential, #create_credential, #create_credential_and_login, #create_credential_login, #db, #db_warning_given?, #get_client, #get_host, #inside_workspace_boundary?, #invalidate_login, #mytask, #myworkspace, #myworkspace_id, #report_auth_info, #report_client, #report_exploit, #report_host, #report_loot, #report_note, #report_service, #report_vuln, #report_web_form, #report_web_page, #report_web_site, #report_web_vuln, #store_cred, #store_local, #store_loot

Methods included from Metasploit::Framework::Require

optionally, optionally_active_record_railtie, optionally_include_metasploit_credential_creation, #optionally_include_metasploit_credential_creation, optionally_require_metasploit_db_gem_engines

Instance Method Details

#get_site_infoObject

We need to know the target sites company name, or FQDN, in order to successfully establish a WebSocket connection. We first favor the user setting either the TargetCompanyName or TargetServerFQDN options. If not set we then try an undocumented API endpoint /get_mech_list, that should return the target site company name. Finally, we fall back on the /download_client_connector endpoint which will also report a servername and site FQDN.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 44

def get_site_info
  if !datastore['TargetCompanyName'].blank? || !datastore['TargetServerFQDN'].blank?
    return {
      company: datastore['TargetCompanyName'],
      server: datastore['TargetServerFQDN']
    }
  end

  site_info = get_site_info_via_mech_list

  return site_info unless site_info.nil?

  get_site_info_via_download_client_connector
end

#get_site_info_via_download_client_connectorObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 97

def get_site_info_via_download_client_connector
  res1 = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'download_client_connector'),
    'vars_get' => {
      'issue_menu' => '1'
    }
  )

  return module_error('get_site_info Connection 1 failed.') unless res1

  return module_error("get_site_info Request 1, unexpected response code #{res1.code}.") unless res1.code == 200

  return module_error('get_site_info_via_download_client_connector Request 1, unable to match data-html-url') unless res1.body =~ %r{data-html-url="\S+(/chat/html/\S+)"}i

  res2 = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, Rex::Text.html_decode(::Regexp.last_match(1)))
  )

  return module_error('get_site_info_via_download_client_connector Connection 2 failed.') unless res2

  return module_error("get_site_info_via_download_client_connector Request 2, unexpected response code #{res2.code}.") unless res2.code == 200

  return module_error('get_site_info_via_download_client_connector Request 2, unable to match data-company.') unless res2.body =~ /data-company="(\S+)"/i

  company = Rex::Text.html_decode(::Regexp.last_match(1))

  return module_error('get_site_info_via_download_client_connector Request 2, unable to match data-servers.') unless res2.body =~ /data-servers="(\S+)"/i

  servers = Rex::Text.html_decode(::Regexp.last_match(1))

  servers_array = JSON.parse(servers)

  return module_error('get_site_info_via_download_client_connector Request 2, data-servers not a valid array.') unless servers_array.instance_of? Array

  return module_error('get_site_info_via_download_client_connector Request 2, data-servers is an empty array.') if servers_array.empty?

  server = servers_array.first

  vprint_status('Got site info via the /download_client_connector endpoint.')

  { company: company, server: server }
rescue JSON::ParserError
  module_error('get_site_info_via_download_client_connector JSON parse error.')
end

#get_site_info_via_mech_listObject

The internal undocumented API located at the /get_mech_list endpoint will return the company name of the target site. We try version=3 (JSON, newer instances) first, then fall back to version=2 (semicolon-separated key=value pairs, for older instances such as 22.x where version=3 returns HTTP 500).



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 62

def get_site_info_via_mech_list
  %w[3 2].each do |version|
    opts = {
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'get_mech_list'),
      'vars_get' => { 'version' => version }
    }
    opts['headers'] = { 'Accept' => 'application/json' } if version == '3'

    res = send_request_cgi(opts)
    next unless res&.code == 200

    company = version == '3' ? parse_mech_list_json(res) : parse_mech_list_text(res)
    next if company.blank?

    vprint_status("Got site info via the /get_mech_list?version=#{version} endpoint.")
    return { company: company, server: nil }
  end

  error('get_site_info_via_mech_list company not found.')
end

#get_versionObject



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 12

def get_version
  res = send_request_cgi(
    'method' => 'GET',
    'uri' => normalize_uri(target_uri.path, 'get_rdf'),
    'vars_get' => {
      'comp' => 'sdcust',
      'locale_code' => 'en-us'
    }
  ) 
  return nil unless res&.code == 200

  header = res.body.match(/^(0 Successful\n.+\n\d+\n)/)
  
  return nil unless header
  
  brdf_data = res.body[header[1].length..]
  
  return nil unless brdf_data.include?('Thank you for using BeyondTrust')

  magic, _, _, prod_version_tag1, file_version_data_len, file_version_tag2 = brdf_data.unpack('NCvCCC')

  return nil unless magic == 0x42524446 # "BRDF" in ASCII
  return nil unless prod_version_tag1 == 0x91
  return nil unless file_version_tag2 == 0x81

  brdf_data[10, file_version_data_len - 1]
end

#initialize(info = {}) ⇒ Object



8
9
10
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 8

def initialize(info={})
  super
end

#module_error(message) ⇒ Object

Helper method to print an error and then return nil.



145
146
147
148
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 145

def module_error(message)
  print_error(message)
  nil
end

#parse_mech_list_json(res) ⇒ Object



84
85
86
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 84

def parse_mech_list_json(res)
  res.get_json_document['company']
end

#parse_mech_list_text(res) ⇒ Object

Parses semicolon-separated key=value pairs (e.g. “company=sewtest;product=ingredi”).



89
90
91
92
93
94
95
# File 'lib/msf/core/exploit/remote/http/beyondtrust.rb', line 89

def parse_mech_list_text(res)
  res.body.split(';').each do |part|
    part.strip!
    return part.sub('company=', '') if part.start_with?('company=')
  end
  nil
end