mirror of
https://github.com/nmap/nmap.git
synced 2025-12-18 21:49:01 +00:00
o [NSE] Added smtp-check-vulns, which currently checks for the Postfix
SMTP server Cyrus SASL authentication memory corruption (CVE-2011-1720).
This commit is contained in:
@@ -160,6 +160,7 @@ Entry { filename = "smb-security-mode.nse", categories = { "discovery", "safe",
|
|||||||
Entry { filename = "smb-server-stats.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-server-stats.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smb-system-info.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "smb-system-info.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "smbv2-enabled.nse", categories = { "default", "safe", } }
|
Entry { filename = "smbv2-enabled.nse", categories = { "default", "safe", } }
|
||||||
|
Entry { filename = "smtp-check-vulns.nse", categories = { "dos", "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "smtp-commands.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "smtp-commands.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "smtp-enum-users.nse", categories = { "discovery", "external", "intrusive", } }
|
Entry { filename = "smtp-enum-users.nse", categories = { "discovery", "external", "intrusive", } }
|
||||||
Entry { filename = "smtp-open-relay.nse", categories = { "discovery", "external", "intrusive", } }
|
Entry { filename = "smtp-open-relay.nse", categories = { "discovery", "external", "intrusive", } }
|
||||||
|
|||||||
381
scripts/smtp-check-vulns.nse
Normal file
381
scripts/smtp-check-vulns.nse
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
description = [[
|
||||||
|
Checks for SMTP, SMTPS and Submission vulnerabilities:
|
||||||
|
|
||||||
|
* Memory corruption in Postfix SMTP server Cyrus SASL support
|
||||||
|
(CVE-2011-1720)
|
||||||
|
http://www.postfix.org/CVE-2011-1720.html
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap --script=smtp-check-vulns --script-args='smtp.domain=<domain>' -pT:25,465,587 <host>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 25/tcp open smtp
|
||||||
|
-- | smtp-check-vulns:
|
||||||
|
-- | Postfix Cyrus SASL (CVE-2011-1720):
|
||||||
|
-- | AUTH MECHANISMS: CRAM-MD5 DIGEST-MD5 NTLM PLAIN LOGIN
|
||||||
|
-- | AUTH tests: CRAM-MD5
|
||||||
|
-- |_ Postfix Cyrus SASL authentication: VULNERABLE (CRAM-MD5 => DIGEST-MD5)
|
||||||
|
--
|
||||||
|
-- @args
|
||||||
|
-- smtp.domain Define the domain to be used in the SMTP EHLO command.
|
||||||
|
|
||||||
|
author = "Djalal Harouni"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"dos", "exploit", "intrusive", "vuln"}
|
||||||
|
|
||||||
|
require "shortport"
|
||||||
|
require "stdnse"
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service({25, 465, 587},
|
||||||
|
{"smtp", "smtps", "submission"})
|
||||||
|
|
||||||
|
local ERROR_MESSAGES = {
|
||||||
|
["EOF"] = "connection closed",
|
||||||
|
["TIMEOUT"] = "connection timeout",
|
||||||
|
["ERROR"] = "failed to receive data"
|
||||||
|
}
|
||||||
|
|
||||||
|
local SMTP_CMD = {
|
||||||
|
["EHLO"] = {
|
||||||
|
cmd = "EHLO",
|
||||||
|
success = {
|
||||||
|
[250] = "Requested mail action okay, completed",
|
||||||
|
},
|
||||||
|
errors = {
|
||||||
|
[421] = "<domain> Service not available, closing transmission channel",
|
||||||
|
[500] = "Syntax error, command unrecognised",
|
||||||
|
[501] = "Syntax error in parameters or arguments",
|
||||||
|
[504] = "Command parameter not implemented",
|
||||||
|
[550] = "Not implemented",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["AUTH"] = {
|
||||||
|
cmd = "AUTH",
|
||||||
|
success = {[334] = ""},
|
||||||
|
errors = {
|
||||||
|
[501] = "Authentication aborted",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
["STARTTLS"] = {
|
||||||
|
cmd = "STARTTLS",
|
||||||
|
success = {
|
||||||
|
[220] = "Ready to start TLS"
|
||||||
|
},
|
||||||
|
errors = {
|
||||||
|
[501] = "Syntax error (no parameters allowed)",
|
||||||
|
[454] = "TLS not available due to temporary reason",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
-- Get a domain to be used in the SMTP commands that need it. If the
|
||||||
|
-- user specified one through a script argument this function will return
|
||||||
|
-- it. Otherwise it will try to find the domain from the typed hostname
|
||||||
|
-- and from the rDNS name. If it still can't find one it will use the
|
||||||
|
-- nmap.scanme.org by default.
|
||||||
|
--
|
||||||
|
-- @param host Current scanned host
|
||||||
|
-- @return The hostname to be used
|
||||||
|
function get_domain(host)
|
||||||
|
local nmap_domain = "nmap.scanme.org"
|
||||||
|
|
||||||
|
-- Use the user provided options.
|
||||||
|
local result = stdnse.get_script_args("smtp.domain") or
|
||||||
|
stdnse.get_script_args("smtp-check-vulns.domain")
|
||||||
|
|
||||||
|
if not result then
|
||||||
|
if type(host) == "table" then
|
||||||
|
if host.targetname then
|
||||||
|
result = host.targetname
|
||||||
|
elseif (host.name ~= "" and host.name) then
|
||||||
|
result = host.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result or nmap_domain
|
||||||
|
end
|
||||||
|
|
||||||
|
local function smtp_finish(socket, status, msg)
|
||||||
|
if socket then
|
||||||
|
socket:send("QUIT\r\n")
|
||||||
|
socket:close()
|
||||||
|
end
|
||||||
|
return status, msg
|
||||||
|
end
|
||||||
|
|
||||||
|
function smtp_send(socket, request)
|
||||||
|
local status, response = socket:send(request)
|
||||||
|
if not status then
|
||||||
|
return status, string.format("failed to send request: %s",
|
||||||
|
request)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true, response
|
||||||
|
end
|
||||||
|
|
||||||
|
function smtp_request(socket, cmd, data)
|
||||||
|
local packet = cmd
|
||||||
|
if data then
|
||||||
|
packet = cmd.." "..data
|
||||||
|
end
|
||||||
|
local status, ret = smtp_send(socket, packet)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(nil, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = socket:receive_lines(1)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(nil, status,
|
||||||
|
(ERROR_MESSAGES[ret] or "unspecified error"))
|
||||||
|
end
|
||||||
|
|
||||||
|
return status, ret
|
||||||
|
end
|
||||||
|
|
||||||
|
function check_smtp_reply(cmd, response)
|
||||||
|
local code, msg = string.match(response, "^([0-9]+)%s*")
|
||||||
|
if code then
|
||||||
|
code = tonumber(code)
|
||||||
|
if SMTP_CMD[cmd] and SMTP_CMD[cmd].success[code] then
|
||||||
|
return true, SMTP_CMD[cmd].success[code]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false, string.format("%s failed: %s", cmd, response)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Checks if the SMTP server is vulnerable to CVE-2011-1720
|
||||||
|
-- Postfix Cyrus SASL authentication memory corruption
|
||||||
|
-- http://www.postfix.org/CVE-2011-1720.html
|
||||||
|
function check_cve_2011_1720(smtp)
|
||||||
|
local postfix_vuln = "Postfix Cyrus SASL authentication"
|
||||||
|
|
||||||
|
local AUTH_VULN = {
|
||||||
|
-- AUTH MECHANISM
|
||||||
|
-- killby: a table of mechanisms that can corrupt and
|
||||||
|
-- overwrite the AUTH MECHANISM data structure.
|
||||||
|
-- probe: max number of probes for each test
|
||||||
|
["CRAM-MD5"] = {
|
||||||
|
killby = {["DIGEST-MD5"] = {probe = 1}}
|
||||||
|
},
|
||||||
|
["DIGEST-MD5"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["EXTERNAL"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["GSSAPI"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["KERBEROS_V4"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["NTLM"] = {
|
||||||
|
killby = {["DIGEST-MD5"] = {probe = 2}}
|
||||||
|
},
|
||||||
|
["OTP"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["PASSDSS-3DES-1"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
["SRP"] = {
|
||||||
|
killby = {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local socket = nmap.new_socket()
|
||||||
|
local status, ret = socket:connect(smtp.host, smtp.port, "tcp")
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
return false, "Couldn't connect to remote host"
|
||||||
|
end
|
||||||
|
|
||||||
|
local i, response = 0, nil
|
||||||
|
-- just a small loop
|
||||||
|
repeat
|
||||||
|
status, response = socket:receive_lines(1)
|
||||||
|
i = i + 1
|
||||||
|
until response or i == 3
|
||||||
|
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(nil, status,
|
||||||
|
(ERROR_MESSAGES[response] or "unspecified error"))
|
||||||
|
end
|
||||||
|
|
||||||
|
status, response = smtp_request(socket, "EHLO",
|
||||||
|
string.format("%s\r\n",smtp.domain))
|
||||||
|
if not status then
|
||||||
|
return status, response
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = check_smtp_reply("EHLO", response)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(socket, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
local starttls = false
|
||||||
|
local function chk_starttls(line)
|
||||||
|
return line:match("STARTTLS")
|
||||||
|
end
|
||||||
|
|
||||||
|
local auth_mech_list, auth_mech_str, chk_vuln = {}, "", false
|
||||||
|
-- parse and check the authentication mechanisms
|
||||||
|
local function chk_auth_mechanisms(line)
|
||||||
|
local authstr = line:match("%d+\-AUTH%s(.*)$")
|
||||||
|
if authstr then
|
||||||
|
auth_mech_str = authstr
|
||||||
|
for mech in authstr:gmatch("[^%s]+") do
|
||||||
|
if AUTH_VULN[mech] then
|
||||||
|
auth_mech_list[mech] = mech
|
||||||
|
if not chk_vuln then
|
||||||
|
chk_vuln = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- parse server response
|
||||||
|
for _, line in pairs(stdnse.strsplit("\r?\n", response)) do
|
||||||
|
if not next(auth_mech_list) then
|
||||||
|
chk_auth_mechanisms(line)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not starttls then
|
||||||
|
starttls = chk_starttls(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- fallback to STARTTLS to get the auth mechanisms
|
||||||
|
if not next(auth_mech_list) and smtp.port.number ~= 25 and
|
||||||
|
starttls then
|
||||||
|
status, response = smtp_request(socket,"STARTTLS\r\n")
|
||||||
|
if not status then
|
||||||
|
return status, response
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = check_smtp_reply("STARTTLS", response)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(socket, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = socket:reconnect_ssl()
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(nil, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
status, response = smtp_request(socket, "EHLO",
|
||||||
|
string.format("%s\r\n",smtp.domain))
|
||||||
|
if not status then
|
||||||
|
return status, response
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = check_smtp_reply("EHLO", response)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(socket, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, line in pairs(stdnse.strsplit("\r?\n", response)) do
|
||||||
|
if not next(auth_mech_list) then
|
||||||
|
chk_auth_mechanisms(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
output.name = "Postfix Cyrus SASL (CVE-2011-1720):"
|
||||||
|
if (#auth_mech_str > 0) then
|
||||||
|
table.insert(output, string.format("AUTH MECHANISMS: %s", auth_mech_str))
|
||||||
|
|
||||||
|
-- maybe vulnerable
|
||||||
|
if next(auth_mech_list) and chk_vuln then
|
||||||
|
|
||||||
|
-- Kill the Postfix smtpd
|
||||||
|
-- Returns true, true if the smtpd was killed
|
||||||
|
local function kill_smtpd(socket, mech, mkill)
|
||||||
|
local killed = false
|
||||||
|
status, response = smtp_request(socket, "AUTH",
|
||||||
|
string.format("%s\r\n", mech))
|
||||||
|
if not status then
|
||||||
|
return status, ret
|
||||||
|
end
|
||||||
|
|
||||||
|
status, ret = check_smtp_reply("AUTH", response)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(socket, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- abort authentication
|
||||||
|
smtp_request(socket, "*\r\n")
|
||||||
|
|
||||||
|
status, response = smtp_request(socket, "AUTH",
|
||||||
|
string.format("%s\r\n", mkill))
|
||||||
|
if not status then
|
||||||
|
if response ~= ERROR_MESSAGES["EOF"] then
|
||||||
|
return status, ret
|
||||||
|
else
|
||||||
|
killed = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- if not killed then abort the last authentication
|
||||||
|
smtp_request(socket, "*\r\n")
|
||||||
|
end
|
||||||
|
return true, killed
|
||||||
|
end
|
||||||
|
|
||||||
|
local auth_tests = ""
|
||||||
|
for mech in pairs(auth_mech_list) do
|
||||||
|
for mkill in pairs(AUTH_VULN[mech].killby) do
|
||||||
|
|
||||||
|
if auth_mech_list[mkill] then
|
||||||
|
auth_tests = auth_tests.." "..mech
|
||||||
|
|
||||||
|
local probe = AUTH_VULN[mech].killby[mkill].probe
|
||||||
|
|
||||||
|
for p = 1, probe do
|
||||||
|
status, ret = kill_smtpd(socket, mech, mkill)
|
||||||
|
if not status then
|
||||||
|
return smtp_finish(nil, status, ret)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ret then
|
||||||
|
table.insert(output,
|
||||||
|
string.format("AUTH tests:%s", auth_tests))
|
||||||
|
table.insert(output,
|
||||||
|
string.format("%s: VULNERABLE (%s => %s)",
|
||||||
|
postfix_vuln, mech, mkill))
|
||||||
|
return smtp_finish(nil, true, output)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(output, string.format("AUTH tests:%s", auth_tests))
|
||||||
|
end
|
||||||
|
else
|
||||||
|
table.insert(output, "Authentication is not available")
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(output, string.format("%s: NOT VULNERABLE", postfix_vuln))
|
||||||
|
return smtp_finish(socket, true, output)
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local smtp_opts = { host = host, port = port }
|
||||||
|
smtp_opts.domain = get_domain(host)
|
||||||
|
local status, output = check_cve_2011_1720(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
|
||||||
Reference in New Issue
Block a user