local nmap = require "nmap" local string = require "string" 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 parsing the returned 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, npndata, protocol 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 local _ _, shlength = bin.unpack(">S", response, 4) local serverhello = string.sub(response, 6, 6 + shlength) -- If server didn't return TLS NPN extension local npnextension, _ = string.find(serverhello, string.char(0x33) .. string.char(0x74)) if not npnextension then stdnse.print_debug(SCRIPT_NAME .. ": Server doesn't support TLS NPN extension.") return results end -- Get NPN data length local _, npnlen = bin.unpack(">S", serverhello:sub(npnextension + 2, npnextension + 3)) if not npnlen then return results end npndata = serverhello:sub(npnextension + 4, npnextension + 4 + npnlen) -- Parse data local i, len = 1 while i < #npndata do len = npndata:byte(i) protocol = npndata:sub(i+1, i+len) table.insert(results, protocol) i = i + len + 1 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