| 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 |
| ]] |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 |
| |
| |
| local conn_timeout = 5 |
| local max_clock_skew = 90*60 |
| |
| |
| local max_clock_jitter = 5 |
| |
| |
| local detail_debug = 2 |
| |
| |
| |
| |
| |
| |
| |
| |
| local client_hello = function(host, port) |
| local sock, status, response, err, cli_h |
| |
| |
| cli_h = tls.client_hello() |
| |
| |
| 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 |
| |
| |
| |
| status, err = sock:send(cli_h) |
| if not status then |
| stdnse.debug("Couldn't send: %s", err) |
| sock:close() |
| return false |
| end |
| |
| |
| 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 |
| |
| |
| 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 |
| |
| |
| |
| |
| |
| |
| |
| |
| local get_time_sample = function (host, port) |
| |
| local rstatus, response = client_hello(host, port) |
| local stm = os.time() |
| if not (rstatus and response) then return nil end |
| |
| 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" } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 = |
| |
| (tchange < 0 or math.abs(tchange - schange) > max_clock_jitter) |
| and result.REJECTED |
| |
| or tchange == 0 |
| and result.STAGNANT |
| |
| 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 |
| |
| |
| |
| local reftm = tm |
| local status |
| status, tm = test_time_sample(host, port, reftm) |
| if status and status == result.STAGNANT then |
| |
| |
| |
| 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 |