diff --git a/CHANGELOG b/CHANGELOG index eea9c1568..3c4779d6b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- + +o [NSE] Added tls-nextprotoneg script which enumerates a TLS server's supported + protocols by using the next protocol negotiation extension. + [Hani Benhabiles] + o [NSOCK] Fixed an epoll-engine-specific bug. The engine didn't recognized FDs that were internally closed and replaced by other ones. This happened during reconnect attempts. [Henri Doreau] diff --git a/scripts/script.db b/scripts/script.db index 6f5dc7d5d..d55bb3283 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -379,6 +379,7 @@ Entry { filename = "targets-traceroute.nse", categories = { "discovery", "safe", Entry { filename = "telnet-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "telnet-encryption.nse", categories = { "discovery", "safe", } } Entry { filename = "tftp-enum.nse", categories = { "discovery", "intrusive", } } +Entry { filename = "tls-nextprotoneg.nse", categories = { "discovery", "safe", "default", } } Entry { filename = "traceroute-geolocation.nse", categories = { "discovery", "external", "safe", } } Entry { filename = "unusual-port.nse", categories = { "safe", } } Entry { filename = "upnp-info.nse", categories = { "default", "discovery", "safe", } } diff --git a/scripts/tls-nextprotoneg.nse b/scripts/tls-nextprotoneg.nse new file mode 100644 index 000000000..e41936949 --- /dev/null +++ b/scripts/tls-nextprotoneg.nse @@ -0,0 +1,171 @@ +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" +local bin = require "bin" +local os = require "os" + +description = [[ +Enumerates a TLS server's supported protocols by using the next protocol negotiation extension. + +This works by adding the next protocol negotiation extension in the client hello +packet and looking for the presence of certain protocols in the server hello's +NPN extension data. + +For more information , see: + * https://tools.ietf.org/html/draft-agl-tls-nextprotoneg-03 +]] + +--- +-- @usage +-- nmap --script=tls-nextprotoneg +-- +--@output +-- 443/tcp open https +-- | tls-nextprotoneg: +-- | spdy/3 +-- | spdy/2 +-- |_ http/1.1 + + +author = "Hani Benhabiles" + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +categories = {"discovery", "safe", "default"} + +portrule = shortport.ssl + + +--- Function that sends a client hello packet with the TLS NPN extension to the +-- target host and returns the response +--@args host The target host table. +--@args port The target port table. +--@return status true if response, false else. +--@return response if status is true. +local client_hello = function(host, port) + local sock, status, response, err, cli_h + + -- Craft Client Hello + -- Content Type: Client Handshake + cli_h = bin.pack(">C", 0x16) + -- Version: TLS 1.0 + cli_h = cli_h .. bin.pack(">S", 0x0301) + -- Length, fixed + cli_h = cli_h .. bin.pack(">S", 0x0037) + -- Handshake protocol + -- Handshake Type: Client Hello + cli_h = cli_h .. bin.pack(">C", 0x01) + -- Length, fixed + cli_h = cli_h .. bin.pack(">CS", 0x00, 0x0033) + -- Version: TLS 1.0 + cli_h = cli_h .. bin.pack(">S", 0x0301) + -- Random: epoch time + cli_h = cli_h .. bin.pack(">I", os.time()) + -- Random: random 28 bytes + cli_h = cli_h .. stdnse.generate_random_string(28) + -- Session ID length + cli_h = cli_h .. bin.pack(">C", 0x00) + -- Cipher Suites length + cli_h = cli_h .. bin.pack(">S", 0x0006) + -- Ciphers + cli_h = cli_h .. bin.pack(">S", 0xc011) + cli_h = cli_h .. bin.pack(">S", 0x0039) + cli_h = cli_h .. bin.pack(">S", 0x0004) + -- Compression Methods length + cli_h = cli_h .. bin.pack(">C", 0x01) + -- Compression Methods: null + cli_h = cli_h .. bin.pack(">C", 0x00) + -- Extensions length + cli_h = cli_h .. bin.pack(">S", 0x0004) + -- TLS NPN Extension + cli_h = cli_h .. bin.pack(">I", 0x33740000) + + -- Connect to the target server + sock = nmap.new_socket() + sock:set_timeout(5000) + status, err = sock:connect(host, port) + if not status then + sock:close() + stdnse.print_debug("Can't send: %s", err) + return false + end + + -- Send Client Hello to the target server + status, err = sock:send(cli_h) + if not status then + stdnse.print_debug("Couldn't send: %s", err) + sock:close() + return false + end + + -- Read response + status, response = sock:receive() + if not status then + stdnse.print_debug("Couldn't receive: %s", err) + sock:close() + return false + end + + return true, response +end + +--- Function that checks for the returned protocols to a npn extension request. +--@args response Response to parse. +--@return results List of found protocols. +local check_npn = function(response) + local results = {} + local shlength + -- List of protocols supported by TLS NPN extension + -- https://tools.ietf.org/html/draft-agl-tls-nextprotoneg-03#section-7 + local protocols = { + "spdy/3", + "spdy/2", + "spdy/1", + "http/1.1", + } + + if not response then + stdnse.print_debug(SCRIPT_NAME .. ": Didn't get response.") + return results + end + -- If content type not handshake + if string.sub(response,1,1) ~= string.char(22) then + stdnse.print_debug(SCRIPT_NAME .. ": Response type not handshake.") + return results + end + -- If handshake protocol not server hello + if string.sub(response, 6, 6) ~= string.char(02) then + stdnse.print_debug(SCRIPT_NAME .. ": Handshake response not server hello.") + return results + end + + -- Get the server hello length + _, shlength = bin.unpack(">S", response, 4) + local serverhello = string.sub(response, 6, 6 + shlength) + + -- If server didn't return TLS NPN extension + if not string.find(serverhello, string.char(0x33) .. string.char(0x74)) then + stdnse.print_debug(SCRIPT_NAME .. ": Server doesn't support TLS NPN extension.") + return results + end + + -- Search for protocols in serverhello + for _, protocol in pairs(protocols) do + if string.find(serverhello, protocol) then + table.insert(results, protocol) + end + end + return results +end + +action = function(host, port) + local status, response + + -- Send crafted client hello + status, response = client_hello(host, port) + if status and response then + -- Analyze response + local results = check_npn(response) + return stdnse.format_output(true, results) + end +end