1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
local shortport = require "shortport"
local stdnse = require "stdnse"
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 <target> --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
-- <elem key="date">2012-08-02T18:29:31+00:00</elem>
-- <elem key="delta">4</elem>
author = {"Aleksandar Nikolic", "nnposter"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe", "default"}
dependencies = {"https-redirect"}
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 connect: %s", err)
return false
end
else
status,sock = specialized_function(host,port)
if not status then
return false
end
end
repeat -- only once
-- Send Client Hello to the target server
status, err = sock:send(cli_h)
if not status then
stdnse.debug("Couldn't send: %s", err)
break
end
-- Read response
status, response, err = tls.record_buffer(sock)
if not status then
stdnse.debug("Couldn't receive: %s", err)
break
end
until true
sock:close()
return status, 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", datetime.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 = datetime.format_timestamp(tm.target, 0),
delta = tm.delta,
}
return output,
string.format("%s; %s from scanner time.", output.date,
datetime.format_difftime(os.date("!*t", tm.target),
os.date("!*t", tm.scanner)))
end
|