mirror of
https://github.com/nmap/nmap.git
synced 2026-01-01 12:29:03 +00:00
o [NSE] Added a sslcert library that gets and caches SSL certificates in the
registry. Modified the scripts ssl-cert and ssl-google-cert-catalog to take advantage of this change. [Patrik]
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added a sslcert library that gets and caches SSL certificates in the
|
||||
registry. Modified the scripts ssl-cert and ssl-google-cert-catalog to take
|
||||
advantage of this change. [Patrik]
|
||||
|
||||
o [NSE] Added host based registry, which allows scripts to share data between
|
||||
scripts scanning a specific host. [Patrik]
|
||||
|
||||
|
||||
208
nselib/sslcert.lua
Normal file
208
nselib/sslcert.lua
Normal file
@@ -0,0 +1,208 @@
|
||||
---
|
||||
-- A library providing functions for collecting SSL certificates and storing
|
||||
-- them in the host-based registry.
|
||||
--
|
||||
-- The library is largely based on code (copy-pasted) from David Fifields
|
||||
-- ssl-cert script in an effort to allow certs to be cached and shared among
|
||||
-- other scripts.
|
||||
--
|
||||
|
||||
module(... or "sslcert", package.seeall)
|
||||
|
||||
require("xmpp")
|
||||
|
||||
StartTLS = {
|
||||
|
||||
ftp_starttls = function(host, port)
|
||||
local s = nmap.new_socket()
|
||||
-- Attempt to negotiate TLS over FTP for services that support it
|
||||
-- Works for FTP (21)
|
||||
|
||||
-- Open a standard TCP socket
|
||||
local status, error = s:connect(host, port, "tcp")
|
||||
local result
|
||||
if not status then
|
||||
return false, "Failed to connect to FTP server"
|
||||
else
|
||||
|
||||
-- Loop until the service presents a banner to deal with server
|
||||
-- load and timing issues. There may be a better way to handle this.
|
||||
local i = 0
|
||||
repeat
|
||||
status, result = s:receive_lines(1)
|
||||
i = i + 1
|
||||
until string.match(result, "^220") or i == 5
|
||||
|
||||
-- Send AUTH TLS command, ask the service to start encryption
|
||||
local query = "AUTH TLS\r\n"
|
||||
status = s:send(query)
|
||||
status, result = s:receive_lines(1)
|
||||
|
||||
if not (string.match(result, "^234")) then
|
||||
stdnse.print_debug("1","%s",result)
|
||||
stdnse.print_debug("1","AUTH TLS failed or unavailable. Enable --script-trace to see what is happening.")
|
||||
|
||||
-- Send QUIT to clean up server side connection
|
||||
local query = "QUIT\r\n"
|
||||
status = s:send(query)
|
||||
result = ""
|
||||
|
||||
return false, "Failed to connect to FTP server"
|
||||
end
|
||||
|
||||
-- Service supports AUTH TLS, tell NSE start SSL negotiation
|
||||
status, error = s:reconnect_ssl()
|
||||
if not status then
|
||||
stdnse.print_debug("1","Could not establish SSL session after AUTH TLS command.")
|
||||
s:close()
|
||||
return false, "Failed to connect to FTP server"
|
||||
end
|
||||
|
||||
end
|
||||
-- Should have a solid TLS over FTP session now...
|
||||
return true, s
|
||||
end,
|
||||
|
||||
smtp_starttls = function(host, port)
|
||||
local s = nmap.new_socket()
|
||||
-- Attempt to negotiate TLS over SMTP for services that support it
|
||||
-- Works for SMTP (25) and SMTP Submission (587)
|
||||
|
||||
-- Open a standard TCP socket
|
||||
local status, error = s:connect(host, port, "tcp")
|
||||
|
||||
if not status then
|
||||
return nil
|
||||
else
|
||||
local resultEHLO
|
||||
-- Loop until the service presents a banner to deal with server
|
||||
-- load and timing issues. There may be a better way to handle this.
|
||||
local i = 0
|
||||
repeat
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
i = i + 1
|
||||
until string.match(resultEHLO, "^220") or i == 5
|
||||
|
||||
-- Send EHLO because the the server expects it
|
||||
-- We are not going to check for STARTTLS in the capabilities
|
||||
-- list, sometimes it is not advertised.
|
||||
local query = "EHLO example.org\r\n"
|
||||
status = s:send(query)
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
|
||||
if not (string.match(resultEHLO, "^250")) then
|
||||
stdnse.print_debug("1","%s",resultEHLO)
|
||||
stdnse.print_debug("1","EHLO with errors or timeout. Enable --script-trace to see what is happening.")
|
||||
return false, "Failed to connect to SMTP server"
|
||||
end
|
||||
|
||||
resultEHLO = ""
|
||||
|
||||
-- Send STARTTLS command ask the service to start encryption
|
||||
local query = "STARTTLS\r\n"
|
||||
status = s:send(query)
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
|
||||
if not (string.match(resultEHLO, "^220")) then
|
||||
stdnse.print_debug("1","%s",resultEHLO)
|
||||
stdnse.print_debug("1","STARTTLS failed or unavailable. Enable --script-trace to see what is happening.")
|
||||
|
||||
-- Send QUIT to clean up server side connection
|
||||
local query = "QUIT\r\n"
|
||||
status = s:send(query)
|
||||
resultEHLO = ""
|
||||
|
||||
return false, "Failed to connect to SMTP server"
|
||||
end
|
||||
|
||||
-- Service supports STARTTLS, tell NSE start SSL negotiation
|
||||
status, error = s:reconnect_ssl()
|
||||
if not status then
|
||||
stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.")
|
||||
s:close()
|
||||
return false, "Failed to connect to SMTP server"
|
||||
end
|
||||
|
||||
end
|
||||
-- Should have a solid TLS over SMTP session now...
|
||||
return true, s
|
||||
end,
|
||||
|
||||
xmpp_starttls = function(host, port)
|
||||
local ls = xmpp.XMPP:new(host, port, { starttls = true } )
|
||||
ls.socket = nmap.new_socket()
|
||||
ls.socket:set_timeout(ls.options.timeout * 1000)
|
||||
|
||||
local status, err = ls.socket:connect(host, port)
|
||||
if not status then
|
||||
return nil
|
||||
end
|
||||
|
||||
status, err = ls:connect()
|
||||
if not(status) then
|
||||
return false, "Failed to connected"
|
||||
end
|
||||
return true, ls.socket
|
||||
end
|
||||
}
|
||||
|
||||
-- A table mapping port numbers to specialized SSL negotiation functions.
|
||||
local SPECIALIZED_FUNCS = {
|
||||
[21] = StartTLS.ftp_starttls,
|
||||
[25] = StartTLS.smtp_starttls,
|
||||
[587] = StartTLS.smtp_starttls,
|
||||
[5222] = StartTLS.xmpp_starttls,
|
||||
[5269] = StartTLS.xmpp_starttls
|
||||
}
|
||||
|
||||
function isPortSupported(port)
|
||||
return SPECIALIZED_FUNCS[port.number]
|
||||
end
|
||||
|
||||
--- Gets a certificate for the given host and port
|
||||
-- The function will attempt to START-TLS for the ports known to require it.
|
||||
-- @param host table as received by the script action function
|
||||
-- @param port table as received by the script action function
|
||||
-- @return status true on success, false on failure
|
||||
-- @return cert userdata containing the SSL certificate, or error message on
|
||||
-- failure.
|
||||
function getCertificate(host, port)
|
||||
local mutex = nmap.mutex("sslcert-cache-mutex")
|
||||
mutex "lock"
|
||||
|
||||
if ( host.registry["ssl-cert"] and
|
||||
host.registry["ssl-cert"][port.number] ) then
|
||||
stdnse.print_debug(2, "sslcert: Returning cached SSL certificate")
|
||||
mutex "done"
|
||||
return true, host.registry["ssl-cert"][port.number]
|
||||
end
|
||||
|
||||
-- Is there a specialized function for this port?
|
||||
local specialized = SPECIALIZED_FUNCS[port.number]
|
||||
local status
|
||||
local socket = nmap.new_socket()
|
||||
if specialized then
|
||||
status, socket = specialized(host, port)
|
||||
|
||||
if not status then
|
||||
mutex "done"
|
||||
return false, "Failed to connect to server"
|
||||
end
|
||||
else
|
||||
local status
|
||||
status = socket:connect(host, port, "ssl")
|
||||
if ( not(status) ) then
|
||||
mutex "done"
|
||||
return false, "Failed to connect to server"
|
||||
end
|
||||
end
|
||||
local cert = socket:get_ssl_certificate()
|
||||
|
||||
host.registry["ssl-cert"] = host.registry["ssl-cert"] or {}
|
||||
host.registry["ssl-cert"][port.number] = host.registry["ssl-cert"][port.number] or {}
|
||||
host.registry["ssl-cert"][port.number] = cert
|
||||
mutex "done"
|
||||
return true, cert
|
||||
end
|
||||
|
||||
|
||||
@@ -64,217 +64,11 @@ license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = { "default", "safe", "discovery" }
|
||||
|
||||
require("nmap")
|
||||
require("nsedebug")
|
||||
require("sslcert")
|
||||
require("shortport")
|
||||
require("stdnse")
|
||||
require("xmpp")
|
||||
|
||||
local stringify_name
|
||||
local date_to_string
|
||||
local table_find
|
||||
local s
|
||||
|
||||
function ftp_starttls(host, port)
|
||||
-- Attempt to negotiate TLS over FTP for services that support it
|
||||
-- Works for FTP (21)
|
||||
|
||||
-- Open a standard TCP socket
|
||||
local status, error = s:connect(host, port, "tcp")
|
||||
|
||||
if not status then
|
||||
return nil
|
||||
else
|
||||
|
||||
-- Loop until the service presents a banner to deal with server
|
||||
-- load and timing issues. There may be a better way to handle this.
|
||||
local i = 0
|
||||
repeat
|
||||
status, result = s:receive_lines(1)
|
||||
i = i + 1
|
||||
until string.match(result, "^220") or i == 5
|
||||
|
||||
-- Send AUTH TLS command, ask the service to start encryption
|
||||
local query = "AUTH TLS\r\n"
|
||||
status = s:send(query)
|
||||
status, result = s:receive_lines(1)
|
||||
|
||||
if not (string.match(result, "^234")) then
|
||||
stdnse.print_debug("1","%s",result)
|
||||
stdnse.print_debug("1","AUTH TLS failed or unavailable. Enable --script-trace to see what is happening.")
|
||||
|
||||
-- Send QUIT to clean up server side connection
|
||||
local query = "QUIT\r\n"
|
||||
status = s:send(query)
|
||||
result = ""
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Service supports AUTH TLS, tell NSE start SSL negotiation
|
||||
status, error = s:reconnect_ssl()
|
||||
if not status then
|
||||
stdnse.print_debug("1","Could not establish SSL session after AUTH TLS command.")
|
||||
s:close()
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
-- Should have a solid TLS over FTP session now...
|
||||
return "Connected"
|
||||
end
|
||||
|
||||
function smtp_starttls(host, port)
|
||||
-- Attempt to negotiate TLS over SMTP for services that support it
|
||||
-- Works for SMTP (25) and SMTP Submission (587)
|
||||
|
||||
-- Open a standard TCP socket
|
||||
local status, error = s:connect(host, port, "tcp")
|
||||
|
||||
if not status then
|
||||
return nil
|
||||
else
|
||||
|
||||
-- Loop until the service presents a banner to deal with server
|
||||
-- load and timing issues. There may be a better way to handle this.
|
||||
local i = 0
|
||||
repeat
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
i = i + 1
|
||||
until string.match(resultEHLO, "^220") or i == 5
|
||||
|
||||
-- Send EHLO because the the server expects it
|
||||
-- We are not going to check for STARTTLS in the capabilities
|
||||
-- list, sometimes it is not advertised.
|
||||
local query = "EHLO example.org\r\n"
|
||||
status = s:send(query)
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
|
||||
if not (string.match(resultEHLO, "^250")) then
|
||||
stdnse.print_debug("1","%s",resultEHLO)
|
||||
stdnse.print_debug("1","EHLO with errors or timeout. Enable --script-trace to see what is happening.")
|
||||
return nil
|
||||
end
|
||||
|
||||
resultEHLO = ""
|
||||
|
||||
-- Send STARTTLS command ask the service to start encryption
|
||||
local query = "STARTTLS\r\n"
|
||||
status = s:send(query)
|
||||
status, resultEHLO = s:receive_lines(1)
|
||||
|
||||
if not (string.match(resultEHLO, "^220")) then
|
||||
stdnse.print_debug("1","%s",resultEHLO)
|
||||
stdnse.print_debug("1","STARTTLS failed or unavailable. Enable --script-trace to see what is happening.")
|
||||
|
||||
-- Send QUIT to clean up server side connection
|
||||
local query = "QUIT\r\n"
|
||||
status = s:send(query)
|
||||
resultEHLO = ""
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Service supports STARTTLS, tell NSE start SSL negotiation
|
||||
status, error = s:reconnect_ssl()
|
||||
if not status then
|
||||
stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.")
|
||||
s:close()
|
||||
return nil
|
||||
end
|
||||
|
||||
end
|
||||
-- Should have a solid TLS over SMTP session now...
|
||||
return "Connected"
|
||||
end
|
||||
|
||||
function xmpp_starttls(host, port)
|
||||
local ls = xmpp.XMPP:new(host, port, { starttls = true } )
|
||||
ls.socket = s
|
||||
ls.socket:set_timeout(ls.options.timeout * 1000)
|
||||
|
||||
local status, err = ls.socket:connect(host, port)
|
||||
if not status then
|
||||
return nil
|
||||
end
|
||||
|
||||
status, err = ls:connect()
|
||||
if status then
|
||||
return "Connected"
|
||||
end
|
||||
end
|
||||
|
||||
-- A table mapping port numbers to specialized SSL negotiation functions.
|
||||
local SPECIALIZED_FUNCS = {
|
||||
[21] = ftp_starttls,
|
||||
[25] = smtp_starttls,
|
||||
[587] = smtp_starttls,
|
||||
[5222] = xmpp_starttls,
|
||||
[5269] = xmpp_starttls
|
||||
}
|
||||
|
||||
portrule = function(host, port)
|
||||
return shortport.ssl(host, port) or SPECIALIZED_FUNCS[port.number]
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
local specialized
|
||||
|
||||
s = nmap.new_socket()
|
||||
|
||||
-- Is there a specialized function for this port?
|
||||
specialized = SPECIALIZED_FUNCS[port.number]
|
||||
if specialized then
|
||||
local status = specialized(host, port)
|
||||
|
||||
if not status then
|
||||
return nil
|
||||
end
|
||||
else
|
||||
local status, error = s:connect(host, port, "ssl")
|
||||
|
||||
if not status then
|
||||
if nmap.verbosity() > 0 then
|
||||
return error
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local cert = s:get_ssl_certificate()
|
||||
s:close()
|
||||
|
||||
local lines = {}
|
||||
lines[#lines + 1] = "Subject: " .. stringify_name(cert.subject)
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "Issuer: " .. stringify_name(cert.issuer)
|
||||
end
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "Public Key type: " .. cert.pubkey.type
|
||||
lines[#lines + 1] = "Public Key bits: " .. cert.pubkey.bits
|
||||
end
|
||||
|
||||
lines[#lines + 1] = "Not valid before: " ..
|
||||
date_to_string(cert.validity.notBefore)
|
||||
lines[#lines + 1] = "Not valid after: " ..
|
||||
date_to_string(cert.validity.notAfter)
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "MD5: " .. stdnse.tohex(cert:digest("md5"), { separator = " ", group = 4 })
|
||||
lines[#lines + 1] = "SHA-1: " .. stdnse.tohex(cert:digest("sha1"), { separator = " ", group = 4 })
|
||||
end
|
||||
|
||||
if nmap.verbosity() > 1 then
|
||||
lines[#lines + 1] = cert.pem
|
||||
end
|
||||
|
||||
add_cert(host, port.number, cert)
|
||||
|
||||
return stdnse.strjoin("\n", lines)
|
||||
return shortport.ssl(host, port) or sslcert.isPortSupported(port)
|
||||
end
|
||||
|
||||
-- Find the index of a value in an array.
|
||||
@@ -288,6 +82,17 @@ function table_find(t, value)
|
||||
return nil
|
||||
end
|
||||
|
||||
function date_to_string(date)
|
||||
if not date then
|
||||
return "MISSING"
|
||||
end
|
||||
if type(date) == "string" then
|
||||
return string.format("Can't parse; string is \"%s\"", date)
|
||||
else
|
||||
return os.date("%Y-%m-%d %H:%M:%S", os.time(date))
|
||||
end
|
||||
end
|
||||
|
||||
-- These are the subject/issuer name fields that will be shown, in this order,
|
||||
-- without a high verbosity.
|
||||
local NON_VERBOSE_FIELDS = { "commonName", "organizationName",
|
||||
@@ -319,25 +124,46 @@ function stringify_name(name)
|
||||
return stdnse.strjoin("/", fields)
|
||||
end
|
||||
|
||||
function date_to_string(date)
|
||||
if not date then
|
||||
return "MISSING"
|
||||
local function parseCertificate(cert)
|
||||
local lines = {}
|
||||
|
||||
lines[#lines + 1] = "Subject: " .. stringify_name(cert.subject)
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "Issuer: " .. stringify_name(cert.issuer)
|
||||
end
|
||||
if type(date) == "string" then
|
||||
return string.format("Can't parse; string is \"%s\"", date)
|
||||
else
|
||||
return os.date("%Y-%m-%d %H:%M:%S", os.time(date))
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "Public Key type: " .. cert.pubkey.type
|
||||
lines[#lines + 1] = "Public Key bits: " .. cert.pubkey.bits
|
||||
end
|
||||
|
||||
lines[#lines + 1] = "Not valid before: " ..
|
||||
date_to_string(cert.validity.notBefore)
|
||||
lines[#lines + 1] = "Not valid after: " ..
|
||||
date_to_string(cert.validity.notAfter)
|
||||
|
||||
if nmap.verbosity() > 0 then
|
||||
lines[#lines + 1] = "MD5: " .. stdnse.tohex(cert:digest("md5"), { separator = " ", group = 4 })
|
||||
lines[#lines + 1] = "SHA-1: " .. stdnse.tohex(cert:digest("sha1"), { separator = " ", group = 4 })
|
||||
end
|
||||
|
||||
if nmap.verbosity() > 1 then
|
||||
lines[#lines + 1] = cert.pem
|
||||
end
|
||||
return lines
|
||||
end
|
||||
|
||||
function add_cert(host, port, cert)
|
||||
if not nmap.registry[host.ip] then
|
||||
nmap.registry[host.ip] = {}
|
||||
end
|
||||
if not nmap.registry[host.ip][port] then
|
||||
nmap.registry[host.ip][port] = {}
|
||||
end
|
||||
action = function(host, port)
|
||||
local status, cert = sslcert.getCertificate(host, port)
|
||||
if ( not(status) ) then
|
||||
return
|
||||
end
|
||||
|
||||
local lines = parseCertificate(cert)
|
||||
|
||||
nmap.registry[host.ip][port]["ssl-cert"] = cert
|
||||
return stdnse.strjoin("\n", lines)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -23,19 +23,13 @@ matching domain name, it may be suspicious. This script requires the
|
||||
author = "Vasiliy Kulikov"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = { "safe", "discovery", "external" }
|
||||
dependencies = { "ssl-cert" }
|
||||
--dependencies = { "ssl-cert" }
|
||||
|
||||
require("nmap")
|
||||
require("shortport")
|
||||
require("stdnse")
|
||||
require("dns")
|
||||
|
||||
|
||||
local get_cert = function(host, port)
|
||||
if nmap.registry[host.ip] and nmap.registry[host.ip][port] then
|
||||
return nmap.registry[host.ip][port]["ssl-cert"]
|
||||
end
|
||||
end
|
||||
require("sslcert")
|
||||
|
||||
local format_date = function(day_num)
|
||||
return os.date("%d %b %Y", 60 * 60 * 24 * tonumber(day_num))
|
||||
@@ -45,9 +39,9 @@ portrule = shortport.ssl
|
||||
|
||||
action = function(host, port)
|
||||
local lines, sha1, query
|
||||
local cert = get_cert(host, port.number)
|
||||
local status, cert = sslcert.getCertificate(host, port)
|
||||
|
||||
if not cert then
|
||||
if not status then
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user