diff --git a/scripts/smtp-commands.nse b/scripts/smtp-commands.nse index 5a7740c75..a97f9928c 100644 --- a/scripts/smtp-commands.nse +++ b/scripts/smtp-commands.nse @@ -13,7 +13,7 @@ SMTP server. -- | smtp-commands: SMTP.domain.com Hello [172.x.x.x], TURN, SIZE, ETRN, PIPELINING, DSN, ENHANCEDSTATUSCODES, 8bitmime, BINARYMIME, CHUNKING, VRFY, X-EXPS GSSAPI NTLM LOGIN, X-EXPS=LOGIN, AUTH GSSAPI NTLM LOGIN, AUTH=LOGIN, X-LINK2STATE, XEXCH50, OK -- |_ This server supports the following commands: HELO EHLO STARTTLS RCPT DATA RSET MAIL QUIT HELP AUTH TURN ETRN BDAT VRFY -- --- @args smtp-commands.domain Define the domain to be used in the SMTP commands. +-- @args smtp.domain or smtp-commands.domain Define the domain to be used in the SMTP commands. -- changelog -- 1.1.0.0 - 2007-10-12 @@ -53,6 +53,8 @@ SMTP server. -- Busleiman's SMTP open relay detector script and Duarte Silva's SMTP -- user enumeration script. -- Props to them for doing what they do and letting me ride on their coattails. +-- 2.1.0.0 - 2011-06-01 +-- + Rewrite the script to use the smtp.lua library. author = "Jason DePriest" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" @@ -60,166 +62,67 @@ categories = {"default", "discovery", "safe"} require "shortport" require "stdnse" -require "comm" +require "smtp" -portrule = shortport.port_or_service({ 25, 465, 587 }, { "smtp", "smtps", "submission" }) - -ERROR_MESSAGES = { - ["EOF"] = "connection closed", - ["TIMEOUT"] = "connection timeout", - ["ERROR"] = "failed to receive data" -} - -STATUS_CODES = { - ERROR = 1, - NOTPERMITED = 2, - VALID = 3, - INVALID = 4 -} - ----Send a command and read the response (this function does exception handling, and if an --- exception occurs, it will close the socket). --- ---@param socket Socket used to send the command ---@param request Command to be sent ---@return False in case of failure ---@return True and the response in case of success -function do_request(socket, request) - -- Exception handler. - local catch = function() - socket:close() - end - - local try = nmap.new_try(catch) - - -- Lets send the command. - try(socket:send(request)) - - -- Receive server response. - local status, response = socket:receive_lines(1) - - if not status then - -- Close the socket (the call to receive_lines doesn't use try). - socket:close() - - return false, (ERROR_MESSAGES[response] or "unspecified error") - end - - return true, response -end - ----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 result = "nmap.scanme.org" - - -- Use the user provided options. - if (nmap.registry.args["smtp-commands.domain"] ~= nil) then - result = nmap.registry.args["smtp-commands.domain"] - elseif type(host) == "table" then - if host.targetname then - result = host.targetname - elseif (host.name ~= "" and host.name) then - result = host.name - end - end - - return result -end +portrule = shortport.port_or_service({ 25, 465, 587 }, + { "smtp", "smtps", "submission" }) function go(host, port) - local socket = nmap.new_socket() - local options = { - timeout = 10000, - recv_before = true - } + local options = { + timeout = 10000, + recv_before = true, + ssl = true, + } - socket:set_timeout(5000) + local domain = stdnse.get_script_args('smtp-commands.domain') or + smtp.get_domain(host) - -- Be polite and when everything works out send the QUIT message. - local quit = function() - do_request(socket, "QUIT\r\n") - socket:close() - end - - local domain = get_domain(host) + local result, status = {} + -- Try to connect to server. + local socket, response = smtp.connect(host, port, options) + if not socket then + return false, string.format("Couldn't establish connection on port %i", + port.number) + end - -- Try to connect to server. - local response + status, response = smtp.ehlo(socket, domain) + if not status then + return status, response + end - socket, response = comm.tryssl(host, port, string.format("EHLO %s\r\n", domain), options) + response = string.gsub(response, "250[%-%s]+", "") -- 250 or 250- + response = string.gsub(response, "\r\n", "\n") -- normalize CR LF + response = string.gsub(response, "\n\r", "\n") -- normalize LF CR + response = string.gsub(response, "^\n+(.-)\n+$", "%1") + response = string.gsub(response, "\n", ", ") -- LF to comma + response = string.gsub(response, "%s+", " ") -- get rid of extra spaces + table.insert(result,response) - if not socket then - return false, string.format("Couldn't establish connection on port %i", port.number) - end - - local result = {} - local index - local status + status, response = smtp.help(socket) + if status then + response = string.gsub(response, "214[%-%s]+", "") -- 214 + response = string.gsub(response, "^%s+(.-)%s+$", "%1") + response = string.gsub(response, "%s+", " ") -- get rid of extra spaces + table.insert(result,response) + smtp.quit(socket) + end - local failure = function(message) - if #result > 0 then - table.insert(result, message) - - return true, result - else - return false, message - end - end - - if not string.match(response, "^250") then - quit() - return false - end - response = string.gsub(response, "250%-", "") -- 250- - response = string.gsub(response, "250 ", "") -- 250 - response = string.gsub(response, "\r\n", "\n") -- normalize CR LF - response = string.gsub(response, "\n\r", "\n") -- normalize LF CR - response = string.gsub(response, "^\n+", "") -- no initial LF - response = string.gsub(response, "\n+$", "") -- no final LF - response = string.gsub(response, "\n", ", ") -- LF to comma - response = string.gsub(response, "%s+", " ") -- get rid of extra spaces - table.insert(result,response) - - status, response = do_request(socket, "HELP\r\n") - - if not status then - return failure(string.format("Failed to issue HELP command (%s)", response)) - end - - if not string.match(response, "^214") then - quit() - return false - end - response = string.gsub(response, "214%-", "") -- 214- - response = string.gsub(response, "214 ", "") -- 214 - response = string.gsub(response, "^%s+", "") -- no initial space - response = string.gsub(response, "%s+$", "") -- no final space - response = string.gsub(response, "%s+", " ") -- get rid of extra spaces - table.insert(result,response) - - quit() - return true, result + return true, result end action = function(host, port) - local status, result = go(host, port) + local status, result = go(host, port) - -- The go function returned false, this means that the result is a simple error message. - if not status then - return result - else - if #result > 0 then - final = {} - for index, test in ipairs(result) do - table.insert(final, test) - end - return stdnse.strjoin("\n ", final) - end - end + -- The go function returned false, this means that the result is a simple error message. + if not status then + return result + else + if #result > 0 then + final = {} + for index, test in ipairs(result) do + table.insert(final, test) + end + return stdnse.strjoin("\n ", final) + end + end end diff --git a/scripts/smtp-enum-users.nse b/scripts/smtp-enum-users.nse index d0818150b..3a37a4f6a 100644 --- a/scripts/smtp-enum-users.nse +++ b/scripts/smtp-enum-users.nse @@ -24,7 +24,7 @@ An example of how to specify the methods to use and the order is the following: -- | smtp-enum-users: -- |_ RCPT, root -- --- @args smtp-enum-users.domain Define the domain to be used in the SMTP commands +-- @args smtp.domain or smtp-enum-users.domain Define the domain to be used in the SMTP commands -- @args smtp-enum-users.methods Define the methods and order to be used by the script (EXPN, VRFY, RCPT) -- changelog @@ -36,6 +36,8 @@ An example of how to specify the methods to use and the order is the following: -- + Script now handles 252 and 550 SMTP status codes -- + Added the method that was used by the script to discover the users if verbosity is -- enabled +-- 2011-06-03 +-- * Rewrite the script to use the smtp.lua library. ----------------------------------------------------------------------- author = "Duarte Silva " @@ -43,155 +45,86 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","external","intrusive"} require "shortport" -require "comm" +require "stdnse" +require "smtp" require "unpwdb" -portrule = shortport.port_or_service({ 25, 465, 587 }, { "smtp", "smtps", "submission" }) - -ERROR_MESSAGES = { - ["EOF"] = "connection closed", - ["TIMEOUT"] = "connection timeout", - ["ERROR"] = "failed to receive data" -} +portrule = shortport.port_or_service({ 25, 465, 587 }, + { "smtp", "smtps", "submission" }) STATUS_CODES = { - ERROR = 1, - NOTPERMITTED = 2, - VALID = 3, - INVALID = 4, - UNKNOWN = 5 + ERROR = 1, + NOTPERMITTED = 2, + VALID = 3, + INVALID = 4, + UNKNOWN = 5 } ----Counts the number of occurrences in a table. Helper function from LUA documentation --- http://lua-users.org/wiki/TableUtils. +---Counts the number of occurrences in a table. Helper function +-- from LUA documentation http://lua-users.org/wiki/TableUtils. -- -- @param from Source table -- @param what What element to count -- @return Number of occurrences function table_count(from, what) - local result = 0 + local result = 0 - for index, item in ipairs(from) do - if item == what then - result = result + 1 - end - end - - return result + for index, item in ipairs(from) do + if item == what then + result = result + 1 + end + end + return result end ----Creates a new table from a source without the duplicates. Helper function from LUA --- documentation http://lua-users.org/wiki/TableUtils. +---Creates a new table from a source without the duplicates. Helper +-- function from LUA documentation http://lua-users.org/wiki/TableUtils. -- -- @param from Source table -- @return New table without the duplicates function table_unique(from) - local result = {} + local result = {} - for index, item in ipairs(from) do - if (table_count(result, item) == 0) then - result[#result + 1] = item - end - end + for index, item in ipairs(from) do + if (table_count(result, item) == 0) then + result[#result + 1] = item + end + end - return result + return result end ----Send a command and read the response (this function does exception handling, and if an --- exception occurs, it will close the socket). --- --- @param socket Socket used to send the command --- @param request Command to be sent --- @return False in case of failure, true and the response in case of success -function do_request(socket, request) - -- Exception handler. - local catch = function() - socket:close() - end - - local try = nmap.new_try(catch) - - -- Lets send the command. - try(socket:send(request)) - - -- Receive server response. - local status, response = socket:receive_lines(1) - - if not status then - -- Close the socket (the call to receive_lines doesn't use try) - socket:close() - - return false, (ERROR_MESSAGES[response] or "unspecified error") - end - - return true, response -end - ----Send a SMTP quit command before closing the socket. --- --- @param socket Socket used to send the command -function quit(socket) - do_request(socket, "QUIT\r\n") - socket:close() -end - ----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 result = "nmap.scanme.org" - - -- Use the user provided options. - if (nmap.registry.args["smtp-enum-users.domain"] ~= nil) then - result = nmap.registry.args["smtp-enum-users.domain"] - elseif type(host) == "table" then - if host.targetname then - result = host.targetname - elseif (host.name ~= "" and host.name) then - result = host.name - end - end - - return result -end - ----Get the method or methods to be used. If the user didn't specify any methods, the default --- order is RCPT, VRFY and then EXPN. +---Get the method or methods to be used. If the user didn't specify any +-- methods, the default order is RCPT, VRFY and then EXPN. -- -- @return A table containing the methods to try function get_method() - local result = {} + local result = {} - if (nmap.registry.args["smtp-enum-users.methods"] ~= nil) then - local methods = nmap.registry.args["smtp-enum-users.methods"] + local methods = stdnse.get_script_args('smtp-enum-users.methods') + if methods and type(methods) == "table" then + -- For each method specified. + for _, method in ipairs(methods) do + -- Are the elements of the argument valid methods. + local upper = string.upper(method) + + if (upper == "RCPT") or (upper == "EXPN") or + (upper == "VRFY") then + table.insert(result, upper) + else + return false, method + end + end + end - if type(methods) == "table" then - -- For each method specified. - for index, method in ipairs(methods) do - -- Are the elements of the argument valid methods. - local upper = string.upper(method) - - if (upper == "RCPT") or (upper == "EXPN") or (upper == "VRFY") then - table.insert(result, upper) - else - return false, method - end - end - end - end + -- The methods weren't specified. + if #result == 0 then + result = { "RCPT", "VRFY", "EXPN" } + else + result = table_unique(result) + end - -- The methods weren't specified. - if #result == 0 then - result = { "RCPT", "VRFY", "EXPN" } - else - result = table_unique(result) - end - - return true, result + return true, result end ---Generic function to perform user discovery. @@ -202,37 +135,44 @@ end -- @param domain Domain to use in the command -- @return Status and depending on the code, a error message function do_gnrc(socket, command, username, domain) - local combinations = { - string.format("%s", username), - string.format("%s@%s", username, domain) - } + local combinations = { + string.format("%s", username), + string.format("%s@%s", username, domain) + } - for index, combination in ipairs(combinations) do - -- Lets try to issue the command. - local status, response = do_request(socket, string.format("%s %s\r\n", command, combination)) + for index, combination in ipairs(combinations) do + -- Lets try to issue the command. + local status, response = smtp.query(socket, command, combination) - -- If this command fails to be sent, then something went wrong with the connection. - if not status then - return STATUS_CODES.ERROR, string.format("Failed to issue %s %s command (%s)\n", command, combination, response) - end + -- If this command fails to be sent, then something + -- went wrong with the connection. + if not status then + return STATUS_CODES.ERROR, + string.format("Failed to issue %s %s command (%s)\n", + command, combination, response) + end - if string.match(response, "^530") then - -- If the command failed, check if authentication is needed because all the other attempts will fail. - return STATUS_CODES.AUTHENTICATION - elseif string.match(response, "^502") or string.match(response, "^252") or string.match(response, "^550") then - -- The server doesn't implement the command or it is disallowed. - return STATUS_CODES.NOTPERMITTED - elseif string.match(response, "^250") then - -- User accepted. - if nmap.verbosity() > 1 then - return STATUS_CODES.VALID, string.format("%s, %s", command, username) - else - return STATUS_CODES.VALID, username - end - end - end + if string.match(response, "^530") then + -- If the command failed, check if authentication is + -- needed because all the other attempts will fail. + return STATUS_CODES.AUTHENTICATION + elseif string.match(response, "^502") or + string.match(response, "^252") or + string.match(response, "^550") then + -- The server doesn't implement the command or it is disallowed. + return STATUS_CODES.NOTPERMITTED + elseif smtp.check_reply(command, response) then + -- User accepted. + if nmap.verbosity() > 1 then + return STATUS_CODES.VALID, + string.format("%s, %s", command, username) + else + return STATUS_CODES.VALID, username + end + end + end - return STATUS_CODES.INVALID + return STATUS_CODES.INVALID end ---Verify if a username is valid using the EXPN command (wrapper @@ -243,7 +183,7 @@ end -- @param domain Domain to use in the command -- @return Status and depending on the code, a error message function do_expn(socket, username, domain) - return do_gnrc(socket, "EXPN", username, domain) + return do_gnrc(socket, "EXPN", username, domain) end ---Verify if a username is valid using the VRFY command (wrapper @@ -254,63 +194,74 @@ end -- @param domain Domain to use in the command -- @return Status and depending on the code, a error message function do_vrfy(socket, username, domain) - return do_gnrc(socket, "VRFY", username, domain) + return do_gnrc(socket, "VRFY", username, domain) end issued_from = false ----Verify if a username is valid using the RCPT method. It will only issue the MAIL FROM --- command if the issued_from flag is false. The MAIL FROM command does not need to --- be issued each time an RCPT TO is used. Otherwise it should also be issued a RSET --- command, and if there are many RSET commands the server might disconnect. +--- Verify if a username is valid using the RCPT method. It will only issue +-- the MAIL FROM command if the issued_from flag is false. The MAIL FROM +-- command does not need to be issued each time an RCPT TO is used. Otherwise +-- it should also be issued a RSET command, and if there are many RSET +-- commands the server might disconnect. -- -- @param socket Socket used to send the command -- @param username User name to test -- @param domain Domain to use in the command -- @return Status and depending on the code, a error message function do_rcpt(socket, username, domain) - if not issued_from then - -- Lets try to issue MAIL FROM command. - status, response = do_request(socket, string.format("MAIL FROM:\r\n", domain)) + if not issued_from then + -- Lets try to issue MAIL FROM command. + status, response = smtp.query(socket, "MAIL", + string.format("FROM:", domain)) - if not status then - -- If this command fails to be sent, then something went wrong with the connection. - return STATUS_CODES.ERROR, string.format("Failed to issue MAIL FROM: command (%s)", domain, response) - elseif string.match(response, "^530") then - -- If the command failed, check if authentication is needed because all the other attempts will fail. - return STATUS_CODES.ERROR, "Couldn't perform user enumeration, authentication needed" - elseif not string.match(response, "^250") then - -- Only accept 250 code as success. - return STATUS_CODES.NOTPERMITTED, "Server did not accept the MAIL FROM command" - end - end + if not status then + -- If this command fails to be sent, then something went wrong + -- with the connection. + return STATUS_CODES.ERROR, + string.format("Failed to issue MAIL FROM: command (%s)", + domain, response) + elseif string.match(response, "^530") then + -- If the command failed, check if authentication is needed + -- because all the other attempts will fail. + return STATUS_CODES.ERROR, + "Couldn't perform user enumeration, authentication needed" + elseif not smtp.check_reply("MAIL", response) then + -- Only accept 250 code as success. + return STATUS_CODES.NOTPERMITTED, + "Server did not accept the MAIL FROM command" + end + end - status, response = do_request(socket, string.format("RCPT TO:<%s@%s>\r\n", username, domain)) + status, response = smtp.query(socket, "RCPT", + string.format("TO:<%s@%s>", username, domain)) - if not status then - return STATUS_CODES.ERROR, string.format("Failed to issue RCPT TO:<%s@%s> command (%s)", username, domain, response) - elseif string.match(response, "^550") then - -- 550 User Unknown - return STATUS_CODES.UNKNOWN - elseif string.match(response, "^553") then - -- 553 Relaying Denied - return STATUS_CODES.NOTPERMITTED - elseif string.match(response, "^530") then - -- If the command failed, check if authentication is needed because all the other attempts will fail. - return STATUS_CODES.AUTHENTICATION - elseif string.match(response, "^250") then - issued_from = true - -- User is valid. - if nmap.verbosity() > 1 then - return STATUS_CODES.VALID, string.format("RCPT, %s", username) - else - return STATUS_CODES.VALID, username - end - end + if not status then + return STATUS_CODES.ERROR, + string.format("Failed to issue RCPT TO:<%s@%s> command (%s)", + username, domain, response) + elseif string.match(response, "^550") then + -- 550 User Unknown + return STATUS_CODES.UNKNOWN + elseif string.match(response, "^553") then + -- 553 Relaying Denied + return STATUS_CODES.NOTPERMITTED + elseif string.match(response, "^530") then + -- If the command failed, check if authentication is needed because + -- all the other attempts will fail. + return STATUS_CODES.AUTHENTICATION + elseif smtp.check_reply("RCPT", response) then + issued_from = true + -- User is valid. + if nmap.verbosity() > 1 then + return STATUS_CODES.VALID, string.format("RCPT, %s", username) + else + return STATUS_CODES.VALID, username + end + end - issued_from = true - - return STATUS_CODES.INVALID + issued_from = true + return STATUS_CODES.INVALID end ---Script function that does all the work. @@ -319,109 +270,108 @@ end -- @param port Target port -- @return The user accounts or a error message. function go(host, port) - -- Get the current usernames list from the file. - local status, nextuser = unpwdb.usernames() + -- Get the current usernames list from the file. + local status, nextuser = unpwdb.usernames() - if not status then - return false, "Failed to read the user names database" - end + if not status then + return false, "Failed to read the user names database" + end - local socket = nmap.new_socket() - socket:set_timeout(5000) + local options = { + timeout = 10000, + recv_before = true, + ssl = true, + } + local domain = stdnse.get_script_args('smtp-enum-users.domain') or + smtp.get_domain(host) + + local methods + status, methods = get_method() + + if not status then + return false, string.format("Invalid method found, %s", methods) + end - local options = { - timeout = 10000, - recv_before = true - } - local domain = get_domain(host) - local methods + local socket, response = smtp.connect(host, port, options) - status, methods = get_method() - - if not status then - return false, string.format("Invalid method found, %s", methods) - end + -- Failed connection attempt. + if not socket then + return false, string.format("Couldn't establish connection on port %i", + port.number) + end - -- Try to connect to server. - local response + status, response = smtp.ehlo(socket, domain) + if not status then + return status, response + end - socket, response = comm.tryssl(host, port, string.format("EHLO %s\r\n", domain), options) + local result = {} - -- Failed connection attempt. - if not socket then - return false, string.format("Couldn't establish connection on port %i", port.number) - end + -- This function is used when something goes wrong with + -- the connection. It makes sure that if it found users before + -- the error occurred, they will be returned. + local failure = function(message) + if #result > 0 then + table.insert(result, message) + return true, result + else + return false, message + end + end - -- Close socket and return if EHLO command failed. - if not string.match(response, "^250") then - quit(socket) - return false, "Failed to issue EHLO command" - end + -- Get the first user to be tested. + local username = nextuser() - local result = {} + for index, method in ipairs(methods) do + while username do + if method == "RCPT" then + status, response = do_rcpt(socket, username, domain) + elseif method == "VRFY" then + status, response = do_vrfy(socket, username, domain) + elseif method == "EXPN" then + status, response = do_expn(socket, username, domain) + end - -- This function is used when something goes wrong with the connection. It makes sure that - -- if it found users before the error occurred, they will be returned. - local failure = function(message) - if #result > 0 then - table.insert(result, message) + if status == STATUS_CODES.NOTPERMITTED then + -- Invalid method. Don't test anymore users with + -- the current method. + break + elseif status == STATUS_CODES.VALID then + -- User found, lets save it. + table.insert(result, response) + elseif status == STATUS_CODES.ERROR then + -- An error occurred with the connection. + return failure(response) + elseif status == STATUS_CODES.AUTHENTICATION then + smtp.quit(socket) + return false, "Couldn't perform user enumeration, authentication needed" + elseif status == STATUS_CODES.INVALID then + table.insert(result, + string.format("Method %s returned a unhandled status code.", + method)) + break + end + username = nextuser() + end + + -- No more users to test, don't test with other methods. + if username == nil then + break + end + end - return true, result - else - return false, message - end - end - - -- Get the first user to be tested. - local username = nextuser() - - for index, method in ipairs(methods) do - while username do - if method == "RCPT" then - status, response = do_rcpt(socket, username, domain) - elseif method == "VRFY" then - status, response = do_vrfy(socket, username, domain) - elseif method == "EXPN" then - status, response = do_expn(socket, username, domain) - end - - if status == STATUS_CODES.NOTPERMITTED then - -- Invalid method. Don't test anymore users with the current method. - break - elseif status == STATUS_CODES.VALID then - -- User found, lets save it. - table.insert(result, response) - elseif status == STATUS_CODES.ERROR then - -- An error occurred with the connection. - return failure(response) - elseif status == STATUS_CODES.AUTHENTICATION then - quit(socket) - return false, "Couldn't perform user enumeration, authentication needed" - elseif status == STATUS_CODES.INVALID then - table.insert(result, string.format("Method %s returned a unhandled status code.", method)) - break - end - - username = nextuser() - end - - if username == nil then - -- No more users to test, don't test with other methods. - break - end - end - - quit(socket) - return true, result + smtp.quit(socket) + return true, result end action = function(host, port) - local status, result = go(host, port) + local status, result = go(host, port) - -- The go function returned true, lets check if it didn't found any accounts. - if status and #result == 0 then - return stdnse.format_output(true, "Couldn't find any accounts") - end + -- The go function returned true, lets check if it + -- didn't found any accounts. + if status and #result == 0 then + return stdnse.format_output(true, "Couldn't find any accounts") + end - return stdnse.format_output(true, result) + return stdnse.format_output(true, result) end diff --git a/scripts/smtp-open-relay.nse b/scripts/smtp-open-relay.nse index ced853df4..7782b6fca 100644 --- a/scripts/smtp-open-relay.nse +++ b/scripts/smtp-open-relay.nse @@ -24,7 +24,7 @@ printed with the list of any combinations that were found prior to the error. -- | smtp-open-relay: Server is an open relay (1/16 tests) -- |_MAIL FROM: -> RCPT TO: -- --- @args smtp-open-relay.domain Define the domain to be used in the anti-spam tests and EHLO command (default +-- @args smtp.domain or smtp-open-relay.domain Define the domain to be used in the anti-spam tests and EHLO command (default -- is nmap.scanme.org) -- @args smtp-open-relay.ip Use this to change the IP address to be used (default is the target IP address) -- @args smtp-open-relay.from Define the source email address to be used (without the domain, default is @@ -63,229 +63,222 @@ printed with the list of any combinations that were found prior to the error. -- * Minor comments changes -- 2010-03-14 Duarte Silva -- * Made the script a little more verbose +-- 2011-06-03 +-- * Rewrite the script to use the smtp.lua library. author = "Arturo 'Buanzo' Busleiman" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery","intrusive","external"} require "shortport" -require "comm" +require "stdnse" +require "smtp" -portrule = shortport.port_or_service({ 25, 465, 587 }, { "smtp", "smtps", "submission" }) - -ERROR_MESSAGES = { - ["EOF"] = "connection closed", - ["TIMEOUT"] = "connection timeout", - ["ERROR"] = "failed to receive data" -} - ----Send a command and read the response (this function does exception handling, and if an --- exception occurs, it will close the socket). --- ---@param socket Socket used to send the command ---@param request Command to be sent ---@return False in case of failure ---@return True and the response in case of success -function do_request(socket, request) - -- Exception handler. - local catch = function() - socket:close() - end - - local try = nmap.new_try(catch) - - -- Lets send the command. - try(socket:send(request)) - - -- Receive server response. - local status, response = socket:receive_lines(1) - - if not status then - -- Close the socket (the call to receive_lines doesn't use try). - socket:close() - - return false, (ERROR_MESSAGES[response] or "unspecified error") - end - - return true, response -end +portrule = shortport.port_or_service({ 25, 465, 587 }, + { "smtp", "smtps", "submission" }) ---Gets the user specified parameters to be used in the tests. -- --@param host Target host (used for the ip parameter default value) --@return Domain, from, to and ip to be used in the tests function get_parameters(host) - local domain, from, to, ip = "nmap.scanme.org", "antispam", "relaytest", host.ip + -- call smtp.get_domain() without the host table to use the + -- 'nmap.scanme.org' host name, we are scanning for open relays. + local domain = stdnse.get_script_args('smtp-open-relay.domain') or + smtp.get_domain() - -- Use the user provided options. - if (nmap.registry.args["smtp-open-relay.domain"] ~= nil) then - domain = nmap.registry.args["smtp-open-relay.domain"] - end - - if (nmap.registry.args["smtp-open-relay.ip"] ~= nil) then - ip = nmap.registry.args["smtp-open-relay.ip"] - end - - if (nmap.registry.args["smtp-open-relay.to"] ~= nil) then - to = nmap.registry.args["smtp-open-relay.to"] - end - - if (nmap.registry.args["smtp-open-relay.from"] ~= nil) then - from = nmap.registry.args["smtp-open-relay.from"] - end - - return domain, from, to, ip + local from = stdnse.get_script_args('smtp-open-relay.from') or "antispam" + + local to = stdnse.get_script_args('smtp-open-relay.to') or "relaytest" + + local ip = stdnse.get_script_args('smtp-open-relay.ip') or host.ip + + return domain, from, to, ip end function go(host, port) - local socket = nmap.new_socket() - local options = { - timeout = 10000, - recv_before = true - } + local options = { + timeout = 10000, + recv_before = true, + ssl = true, + } - socket:set_timeout(5000) + local result, status, index = {} - -- Be polite and when everything works out send the QUIT message. - local quit = function() - do_request(socket, "QUIT\r\n") - socket:close() - end - - local domain, from, to, ip = get_parameters(host) + local domain, from, to, ip = get_parameters(host) - -- Try to connect to server. - local response + local socket, response = smtp.connect(host, port, options) + if not socket then + return false, string.format("Couldn't establish connection on port %i", + port.number) + end - socket, response = comm.tryssl(host, port, string.format("EHLO %s\r\n", domain), options) + local srvname = string.match(response, "%d+%s([%w]+[%w\.\-]*)") - if not socket then - return false, string.format("Couldn't establish connection on port %i", port.number) - end + local status, response = smtp.ehlo(socket, domain) + if not status then + return status, response + end + + if not srvname then + srvname = string.match(response, "%d+%-([%w]+[%w\.\-]*)") + end - -- Close socket and return if EHLO command failed. - if not string.match(response, "^250") then - quit() - return false, "Failed to issue EHLO command" - end + -- Antispam tests. + local tests = { + { + from = "", + to = string.format("%s@%s", to, domain) + }, + { + from = string.format("%s@%s", from, domain), + to = string.format("%s@%s", to, domain) + }, + { + from = string.format("%s@%s", from, srvname), + to = string.format("%s@%s", to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s@%s", to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s%%%s@[%s]", to, domain, ip) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s%%%s@%s", to, domain, srvname) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("\"%s@%s\"", to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("\"%s%%%s\"", to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s@%s@[%s]", to, domain, ip) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("\"%s@%s\"@[%s]", to, domain, ip) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s@%s@%s", to, domain, srvname) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("@[%s]:%s@%s", ip, to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("@%s:%s@%s", srvname, to, domain) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s!%s", domain, to) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s!%s@[%s]", domain, to, ip) + }, + { + from = string.format("%s@[%s]", from, ip), + to = string.format("%s!%s@%s", domain, to, srvname) + }, + } + + -- This function is used when something goes wrong with the connection. + -- It makes sure that if it found working combinations before the error + -- occurred, they will be returned. If the debug flag is enabled the + -- error message will be appended to the combinations list. + local failure = function(message) + if #result > 0 then + table.insert(result, message) + return true, result + else + return false, message + end + end - -- Find out server name. - local srvname = string.sub(response, string.find(response, '([.%w]+)', 4)) - - -- Antispam tests. - local tests = { - { from = "MAIL FROM:<>", to = string.format("RCPT TO:<%s@%s>", to, domain) }, - { from = string.format("MAIL FROM:<%s@%s>", from, domain), to = string.format("RCPT TO:<%s@%s>", to, domain) }, - { from = string.format("MAIL FROM:<%s@%s>", from, srvname), to = string.format("RCPT TO:<%s@%s>", to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s@%s>", to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s%%%s@[%s]>", to, domain, ip) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s%%%s@%s>", to, domain, srvname) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<\"%s@%s\">", to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<\"%s%%%s\">", to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s@%s@[%s]>", to, domain, ip) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<\"%s@%s\"@[%s]>", to, domain, ip) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s@%s@%s>", to, domain, srvname) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<@[%s]:%s@%s>", ip, to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<@%s:%s@%s>", srvname, to, domain) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s!%s>", domain, to) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s!%s@[%s]>", domain, to, ip) }, - { from = string.format("MAIL FROM:<%s@[%s]>", from, ip), to = string.format("RCPT TO:<%s!%s@%s>", domain, to, srvname) }, - } - - local result = {} - local index - local status - - -- This function is used when something goes wrong with the connection. It makes sure that - -- if it found working combinations before the error occurred, they will be returned. If the - -- debug flag is enabled the error message will be appended to the combinations list. - local failure = function(message) - if #result > 0 then - table.insert(result, message) + for index = 1, #tests do + status, response = smtp.reset(socket) + if not status then + if string.match(response, "530") then + return false, "Server isn't an open relay, authentication needed" + end + return failure(response) + end - return true, result - else - return false, message - end - end - - for index = 1, #tests do - status, response = do_request(socket, "RSET\r\n") + status, response = smtp.query(socket, "MAIL", + string.format("FROM:<%s>", + tests[index]["from"])) + -- If this command fails to be sent, then something went + -- wrong with the connection. + if not status then + return failure(string.format("Failed to issue %s command (%s)", + tests[index]["from"], response)) + end + + if string.match(response, "530") then + smtp.quit(socket) + return false, "Server isn't an open relay, authentication needed" + elseif smtp.check_reply("MAIL", response) then + -- Lets try to actually relay. + status, response = smtp.query(socket, "RCPT", + string.format("TO:<%s>", + tests[index]["to"])) + if not status then + return failure(string.format("Failed to issue %s command (%s)", + tests[index]["to"], response)) + end - if not status then - return failure(string.format("Failed to issue RSET command (%s)", response)) - end + if string.match(response, "530") then + smtp.quit(socket) + return false, "Server isn't an open relay, authentication needed" + elseif smtp.check_reply("RCPT", response) then + -- Save the working from and to combination. + table.insert(result, + string.format("MAIL FROM:<%s> -> RCPT TO:<%s>", + tests[index]["from"], tests[index]["to"])) + end + end + end - -- If reset the envelope, doesn't work for one, wont work for others (critical command). - if not string.match(response, "^250") then - quit() - - if string.match(response, "^530") then - return false, "Server isn't an open relay, authentication needed" - else - return false, "Unable to clear server envelope, testing stoped" - end - end - - -- Lets try to issue MAIL FROM command. - status, response = do_request(socket, string.format("%s\r\n", tests[index]["from"])) - - -- If this command fails to be sent, then something went wrong with the connection. - if not status then - return failure(string.format("Failed to issue %s command (%s)", tests[index]["from"], response)) - end - - -- If MAIL FROM failed, check if authentication is needed because all the other attempts will fail - -- and server may disconnect because of too many commands issued without authentication. - if string.match(response, "^530") then - quit() - return false, "Server isn't an open relay, authentication needed" - -- The command was accepted (otherwise, the script will step to the next test). - elseif string.match(response, "^250") then - -- Lets try to actually relay. - status, response = do_request(socket, string.format("%s\r\n", tests[index]["to"])) - - if not status then - return failure(string.format("Failed to issue %s command (%s)", tests[index]["to"], response)) - end - - if string.match(response, "^530") then - quit() - return false, "Server isn't an open relay, authentication needed" - elseif string.match(response, "^250") then - -- Save the working from and to combination. - table.insert(result, string.format("%s -> %s", tests[index]["from"], tests[index]["to"])) - end - end - end - - quit() - return true, result + smtp.quit(socket) + return true, result end action = function(host, port) - local status, result = go(host, port) + local status, result = go(host, port) - -- The go function returned false, this means that the result is a simple error message. - if not status then - return result - else - -- Combinations were found. If verbosity is active, the script will print all - -- the successful tests. Otherwise it will only print the conclusion. - if #result > 0 then - final = {} + -- The go function returned false, this means that the result is + -- a simple error message. + if not status then + return result + else + -- Combinations were found. If verbosity is active, the script + -- will print all the successful tests. Otherwise it will only + -- print the conclusion. + if #result > 0 then + final = {} + table.insert(final, + string.format("Server is an open relay (%i/16 tests)", + (#result))) - table.insert(final, string.format("Server is an open relay (%i/16 tests)", (#result))) + if nmap.verbosity() > 1 then + for index, test in ipairs(result) do + table.insert(final, test) + end + end - if nmap.verbosity() > 1 then - for index, test in ipairs(result) do - table.insert(final, test) - end - end + return stdnse.strjoin("\n ", final) + end - return stdnse.strjoin("\n ", final) - end - - return "Server doesn't seem to be an open relay, all tests failed" - end + return "Server doesn't seem to be an open relay, all tests failed" + end end diff --git a/scripts/smtp-vuln-cve2011-1720.nse b/scripts/smtp-vuln-cve2011-1720.nse index 067eb74f3..fb4539b00 100644 --- a/scripts/smtp-vuln-cve2011-1720.nse +++ b/scripts/smtp-vuln-cve2011-1720.nse @@ -1,9 +1,9 @@ description = [[ -Checks for SMTP, SMTPS and Submission vulnerabilities: +Checks for a Memory corruption in the Postfix SMTP server when it uses +Cyrus SASL library authentication mechanisms (CVE-2011-1720). -* Memory corruption in Postfix SMTP server Cyrus SASL support - (CVE-2011-1720) - http://www.postfix.org/CVE-2011-1720.html +Reference: +* http://www.postfix.org/CVE-2011-1720.html ]] --- @@ -19,270 +19,167 @@ Checks for SMTP, SMTPS and Submission vulnerabilities: -- | 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. +-- @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 = {"intrusive", "vuln"} require "shortport" +require "smtp" 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] = " 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", - }, +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}} }, - ["AUTH"] = { - cmd = "AUTH", - success = {[334] = ""}, - errors = { - [501] = "Authentication aborted", - } + ["DIGEST-MD5"] = { + killby = {} + }, + ["EXTERNAL"] = { + killby = {} + }, + ["GSSAPI"] = { + killby = {} + }, + ["KERBEROS_V4"] = { + killby = {} + }, + ["NTLM"] = { + killby = {["DIGEST-MD5"] = {probe = 2}} + }, + ["OTP"] = { + killby = {} + }, + ["PASSDSS-3DES-1"] = { + killby = {} + }, + ["SRP"] = { + killby = {} }, - ["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-vuln-cve2011-1720.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 + +-- parse and check the authentication mechanisms. +-- This function will save the vulnerable auth mechanisms in +-- the auth_mlist table, and returns all the available auth +-- mechanisms as a string. +local function chk_auth_mechanisms(ehlo_res, auth_mlist) + local mlist, mstr = smtp.get_auth_mech(ehlo_res), "" + + if mlist then + for _, mech in ipairs(mlist) do + mstr = mstr.." "..mech + if AUTH_VULN[mech] then + auth_mlist[mech] = mech end end end - - return result or nmap_domain + return mstr end +-- Close any remaining connection local function smtp_finish(socket, status, msg) if socket then - socket:send("QUIT\r\n") - socket:close() + smtp.quit(socket) end return status, msg end -function smtp_send(socket, request) - local status, response = socket:send(request) +-- Tries to kill the smtpd server +-- Returns true, true if the smtpd was killed +local function kill_smtpd(socket, mech, mkill) + local killed, ret = false + local status, response = smtp.query(socket, "AUTH", + string.format("%s", mech)) if not status then - return status, string.format("failed to send request: %s", - request) + return status, response 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) + status, ret = smtp.check_reply("AUTH", response) if not status then - return smtp_finish(nil, status, ret) + return smtp_finish(socket, status, ret) + end + + -- abort authentication + smtp.query(socket, "*") + + status, response = smtp.query(socket, "AUTH", + string.format("%s", mkill)) + if status then + -- abort the last AUTH command. + status, response = smtp.query(socket, "*") 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] + if string.match(response, "connection closed") then + killed = true + else + return status, response end end - return false, string.format("%s failed: %s", cmd, response) + + return true, killed 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 function check_smtpd(smtp_opts) 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") + local socket, ret = smtp.connect(smtp_opts.host, + smtp_opts.port, + {ssl = false, + recv_before = true, + lines = 1}) - if not status then - return false, "Couldn't connect to remote host" + if not socket then + return socket, ret 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)) + local status, response = smtp.ehlo(socket, smtp_opts.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 + local auth_mech_list, auth_mech_str = {}, "" -- parse server response for _, line in pairs(stdnse.strsplit("\r?\n", response)) do if not next(auth_mech_list) then - chk_auth_mechanisms(line) + auth_mech_str = chk_auth_mechanisms(line, auth_mech_list) end if not starttls then - starttls = chk_starttls(line) + starttls = line:match("STARTTLS") end end -- fallback to STARTTLS to get the auth mechanisms - if not next(auth_mech_list) and smtp.port.number ~= 25 and + if not next(auth_mech_list) and smtp_opts.port.number ~= 25 and starttls then - status, response = smtp_request(socket,"STARTTLS\r\n") + + status, response = smtp.starttls(socket) 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)) + + status, response = smtp.ehlo(socket, smtp_opts.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) + auth_mech_str = chk_auth_mechanisms(line, auth_mech_list) end end end @@ -293,42 +190,9 @@ function check_cve_2011_1720(smtp) 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 - + if next(auth_mech_list) then local auth_tests = "" + for mech in pairs(auth_mech_list) do for mkill in pairs(AUTH_VULN[mech].killby) do @@ -370,9 +234,13 @@ function check_cve_2011_1720(smtp) 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) + local smtp_opts = { + host = host, + port = port, + domain = stdnse.get_script_args('smtp-vuln-cve2011-1720.domain') or + smtp.get_domain(host), + } + local status, output = check_smtpd(smtp_opts) if not status then stdnse.print_debug(1, "%s: %s", SCRIPT_NAME, output) return nil