1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-30 03:19:02 +00:00

o [NSE] Updated the SMTP scripts to use the new SMTP Lua library.

This commit is contained in:
djalal
2011-06-13 13:19:26 +00:00
parent e7c6bf55f0
commit caadf952e3
4 changed files with 574 additions and 860 deletions

View File

@@ -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:<antispam@insecure.org> -> RCPT TO:<relaytest@insecure.org>
--
-- @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 <duarte.silva@myf00.net>
-- * 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