local shortport = require "shortport" local stdnse = require "stdnse" local table = require "table" local math = require "math" local nmap = require "nmap" local os = require "os" local string = require "string" local sslcert = require "sslcert" local tls = require "tls" local datetime = require "datetime" description = [[ Retrieves a target host's time and date from its TLS ServerHello response. In many TLS implementations, the first four bytes of server randomness are a Unix timestamp. The script will test whether this is indeed true and report the time only if it passes this test. Original idea by Jacob Appelbaum and his TeaTime and tlsdate tools: * https://github.com/ioerror/TeaTime * https://github.com/ioerror/tlsdate ]] --- -- @usage -- nmap --script=ssl-date -- -- @output -- PORT STATE SERVICE REASON -- 5222/tcp open xmpp-client syn-ack -- |_ssl-date: 2012-08-02T18:29:31Z; +4s from local time. -- -- @xmloutput -- 2012-08-02T18:29:31+00:00 -- 4 author = {"Aleksandar Nikolic", "nnposter"} license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"discovery", "safe", "default"} portrule = function(host, port) return shortport.ssl(host, port) or sslcert.getPrepareTLSWithoutReconnect(port) end -- Miscellaneous script-wide constants local conn_timeout = 5 -- connection timeout (seconds) local max_clock_skew = 90*60 -- maximum acceptable difference between target -- and scanner clocks to avoid additional -- testing (seconds) local max_clock_jitter = 5 -- maximum acceptable target clock jitter -- Logically should be 50-100% of conn_timeout -- (seconds) local detail_debug = 2 -- debug level for printing detailed steps --- Function that sends a client hello packet -- 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 cli_h = tls.client_hello() -- Connect to the target server local specialized_function = sslcert.getPrepareTLSWithoutReconnect(port) if not specialized_function then sock = nmap.new_socket() sock:set_timeout(1000 * conn_timeout) status, err = sock:connect(host, port) if not status then sock:close() stdnse.debug("Can't send: %s", err) return false end else status,sock = specialized_function(host,port) if not status then return false end end -- Send Client Hello to the target server status, err = sock:send(cli_h) if not status then stdnse.debug("Couldn't send: %s", err) sock:close() return false end -- Read response status, response, err = tls.record_buffer(sock) if not status then stdnse.debug("Couldn't receive: %s", err) sock:close() return false end return true, response end -- extract time from ServerHello response local extract_time = function(response) local i, record = tls.record_read(response, 1) if record == nil then stdnse.debug("Unknown response from server") return nil end if record.type == "handshake" then for _, body in ipairs(record.body) do if body.type == "server_hello" then return true, body.time end end end stdnse.debug("Server response was not server_hello") return nil end --- -- Retrieve a timestamp from a TLS port and compare it to the scanner clock -- -- @param host TLS host -- @param port TLS port -- @return Timestamp sample object or nil (if the operation failed) local get_time_sample = function (host, port) -- Send crafted client hello local rstatus, response = client_hello(host, port) local stm = os.time() if not (rstatus and response) then return nil end -- extract time from response local tstatus, ttm = extract_time(response) if not tstatus then return nil end stdnse.debug(detail_debug, "TLS sample: %s", stdnse.format_timestamp(ttm, 0)) return {target=ttm, scanner=stm, delta=os.difftime(ttm, stm)} end local result = { STAGNANT = "stagnant", ACCEPTED = "accepted", REJECTED = "rejected" } --- -- Obtain a new timestamp sample and validate it against a reference sample -- -- @param host TLS host -- @param port TLS port -- @param reftm Reference timestamp sample -- @return Result code -- @return New timestamp sample object or nil (if the operation failed) local test_time_sample = function (host, port, reftm) local tm = get_time_sample(host, port) if not tm then return nil end local tchange = os.difftime(tm.target, reftm.target) local schange = os.difftime(tm.scanner, reftm.scanner) local status = -- clock cannot run backwards or drift rapidly (tchange < 0 or math.abs(tchange - schange) > max_clock_jitter) and result.REJECTED -- the clock did not advance or tchange == 0 and result.STAGNANT -- plausible enough or result.ACCEPTED stdnse.debug(detail_debug, "TLS sample verdict: %s", status) return status, tm end action = function(host, port) local tm = get_time_sample(host, port) if not tm then return stdnse.format_output(false, "Unable to obtain data from the target") end if math.abs(tm.delta) > max_clock_skew then -- The target clock differs substantially from the scanner -- Let's take another sample to eliminate cases where the TLS field -- contains either random or fixed data instead of the timestamp local reftm = tm local status status, tm = test_time_sample(host, port, reftm) if status and status == result.STAGNANT then -- The target clock did not advance between the two samples (reftm, tm) -- Let's wait long enough for the target clock to advance -- and then re-take the second sample stdnse.sleep(1.1) status, tm = test_time_sample(host, port, reftm) end if not status then return nil end if status ~= result.ACCEPTED then return {}, "TLS randomness does not represent time" end end datetime.record_skew(host, tm.target, tm.scanner) local output = { date = stdnse.format_timestamp(tm.target, 0), delta = tm.delta, } return output, string.format("%s; %s from scanner time.", output.date, stdnse.format_difftime(os.date("!*t", tm.target), os.date("!*t", tm.scanner))) end