1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-24 00:19:01 +00:00

sslcert: Try tls.lua if Nsock's SSL routines fail to connect.

This commit is contained in:
dmiller
2020-01-06 18:24:33 +00:00
parent b9d14a40ab
commit 81ceee49c5

View File

@@ -863,9 +863,12 @@ local function get_record_iter(sock)
local i = 1
local fragment
return function ()
local record
i, record = tls.record_read(buffer, i, fragment)
local record, more
i, record, more = tls.record_read(buffer, i, fragment)
if record == nil then
if not more then
return nil, "no more"
end
local status, err
status, buffer, err = tls.record_buffer(sock, buffer, i)
if not status then
@@ -881,6 +884,57 @@ local function get_record_iter(sock)
end
end
local function handshake_cert (socket)
-- logic mostly lifted from ssl-enum-ciphers
local hello = tls.client_hello()
local status, err = socket:send(hello)
if not status then
return false, "Failed to send to server"
end
local get_next_record = get_record_iter(socket)
local records = {}
local done = false
while not done do
local record
record, err = get_next_record()
if not record then
stdnse.debug1("no record: %s", err)
break
end
-- Collect message bodies into one record per type
records[record.type] = records[record.type] or record
for j = 1, #record.body do -- no ipairs because we append below
local b = record.body[j]
done = ((record.type == "alert" and b.level == "fatal") or
(record.type == "handshake" and b.type == "server_hello_done"))
table.insert(records[record.type].body, b)
end
end
local handshake = records.handshake
if not handshake then
return false, "Server did not handshake"
end
local certs
for i, b in ipairs(handshake.body) do
if b.type == "certificate" then
certs = b
break
end
end
if not certs or not next(certs.certificates) then
return false, "Server sent no certificate"
end
local cert, err = parse_ssl_certificate(certs.certificates[1])
if not cert then
return false, ("Unable to parse cert: %s"):format(err)
end
return true, cert
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
@@ -901,106 +955,70 @@ function getCertificate(host, port)
local cert
local wrapper = SPECIALIZED_WRAPPED_TLS_WITHOUT_RECONNECT[port.service] or SPECIALIZED_WRAPPED_TLS_WITHOUT_RECONNECT[port.number]
local specialized = SPECIALIZED_PREPARE_TLS[port.service] or SPECIALIZED_PREPARE_TLS[port.number]
local status = false
-- If we don't already know the service is TLS wrapped check to see if we
-- have to use a wrapper and do a manual handshake
local wrapper
if not ( port.version.service_tunnel == 'ssl' ) then
wrapper = SPECIALIZED_WRAPPED_TLS_WITHOUT_RECONNECT[port.service] or SPECIALIZED_WRAPPED_TLS_WITHOUT_RECONNECT[port.number]
if wrapper and port.version.service_tunnel ~= 'ssl' then
local socket
status, socket = wrapper(host, port)
if not status then
stdnse.debug1("Wrapper function error: %s", socket)
else
status, cert = handshake_cert(socket)
socket:close()
end
end
if wrapper then
local status, socket = wrapper(host, port)
-- If that didn't work, see if we need a specialized connection method
if not status and specialized and port.version.service_tunnel ~= 'ssl' then
local socket
status, socket = specialized(host, port)
if not status then
mutex "done"
return false, socket
end
-- logic mostly lifted from ssl-enum-ciphers
local hello = tls.client_hello()
local status, err = socket:send(hello)
if not status then
mutex "done"
return false, "Failed to connect to server"
end
local get_next_record = get_record_iter(socket)
local records = {}
while true do
local record
record, err = get_next_record()
if not record then
stdnse.debug1("no record: %s", err)
socket:close()
break
end
-- Collect message bodies into one record per type
records[record.type] = records[record.type] or record
local done = false
for j = 1, #record.body do -- no ipairs because we append below
local b = record.body[j]
done = ((record.type == "alert" and b.level == "fatal") or
(record.type == "handshake" and b.type == "server_hello_done"))
table.insert(records[record.type].body, b)
end
if done then
socket:close()
break
end
end
local handshake = records.handshake
if not handshake then
mutex "done"
return false, "Server did not handshake"
end
local certs
for i, b in ipairs(handshake.body) do
if b.type == "certificate" then
certs = b
break
end
end
if not certs or not next(certs.certificates) then
mutex "done"
return false, "Server sent no certificate"
end
cert, err = parse_ssl_certificate(certs.certificates[1])
if not cert then
mutex "done"
return false, ("Unable to get cert: %s"):format(err)
end
else
-- If we don't already know the service is TLS wrapped check to see if
-- there a specialized function for this port
local specialized
if not ( port.version.service_tunnel == 'ssl' ) then
specialized = SPECIALIZED_PREPARE_TLS[port.service] or SPECIALIZED_PREPARE_TLS[port.number]
end
local status
local socket = nmap.new_socket()
if specialized then
status, socket = specialized(host, port)
if not status then
mutex "done"
stdnse.debug1("Specialized function error: %s", socket)
return false, "Failed to connect to server"
end
stdnse.debug1("Specialized function error: %s", socket)
else
status = socket:connect(host, port, "ssl")
if ( not(status) ) then
mutex "done"
return false, "Failed to connect to server"
end
cert = socket:get_ssl_certificate()
status = not not cert
socket:close()
end
cert = socket:get_ssl_certificate()
if ( cert == nil ) then
mutex "done"
return false, "Unable to get cert"
end
-- Now try to connect with Nsock's SSL connection
if not status then
local socket = nmap.new_socket()
local errmsg
status, errmsg = socket:connect(host, port, "ssl")
if not status then
stdnse.debug1("SSL connect error: %s", errmsg)
else
cert = socket:get_ssl_certificate()
status = not not cert
socket:close()
end
end
-- Finally, try to connect and manually handshake (maybe more tolerant of TLS
-- insecurity than OpenSSL)
if not status then
local socket = nmap.new_socket()
local errmsg
status, errmsg = socket:connect(host, port)
if not status then
stdnse.debug1("Connect error: %s", errmsg)
else
status, cert = handshake_cert(socket)
socket:close()
end
end
if not status then
mutex "done"
return false, "No certificate found"
end
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