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", "http1.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