diff --git a/nselib/http.lua b/nselib/http.lua index 958bf8d92..d3e14371e 100644 --- a/nselib/http.lua +++ b/nselib/http.lua @@ -428,6 +428,63 @@ request = function( host, port, data, options ) end +local MONTH_MAP = { + Jan = 1, Feb = 2, Mar = 3, Apr = 4, May = 5, Jun = 6, + Jul = 7, Aug = 8, Sep = 9, Oct = 10, Nov = 11, Dec = 12 +} + +--- Parses an HTTP date string, in any of the following formats from section +-- 3.3.1 of RFC 2616: +-- * Sun, 06 Nov 1994 08:49:37 GMT (RFC 822, updated by RFC 1123) +-- * Sunday, 06-Nov-94 08:49:37 GMT (RFC 850, obsoleted by RFC 1036) +-- * Sun Nov 6 08:49:37 1994 (ANSI C's asctime() format) +-- @arg s the date string. +-- @return a table with keys year, month, +-- day, hour, min, sec, and +-- isdst, relative to GMT, suitable for input to +-- os.time. +function parse_date(s) + local day, month, year, hour, min, sec, tz, month_name + -- RFC 2616, section 3.3.1: + + -- Handle RFC 1123 and 1036 at once. + day, month_name, year, hour, min, sec, tz = s:match("^%w+, (%d+)[- ](%w+)[- ](%d+) (%d+):(%d+):(%d+) (%w+)$") + if not day then + month_name, day, hour, min, sec, year = s:match("%w+ (%w+) ?(%d+) (%d+):(%d+):(%d+) (%d+)") + tz = "GMT" + end + if not day then + stdnse.print_debug(1, "http.parse_date: can't parse date \"%s\": unknown format.", s) + return nil + end + -- Look up the numeric code for month. + month = MONTH_MAP[month_name] + if not month then + stdnse.print_debug(1, "http.parse_date: unknown month name \"%s\".", month_name) + return nil + end + if tz ~= "GMT" then + stdnse.print_debug(1, "http.parse_date: don't know time zone \"%s\", only \"GMT\".", tz) + return nil + end + day = tonumber(day) + year = tonumber(year) + hour = tonumber(hour) + min = tonumber(min) + sec = tonumber(sec) + + if year < 100 then + -- Two-digit year. Make a guess. + if year < 70 then + year = year + 2000 + else + year = year + 1900 + end + end + + return { year = year, month = month, day = day, hour = hour, min = min, sec = sec, isdst = false } +end + get_default_timeout = function( nmap_timing ) local timeout = {} if nmap_timing >= 0 and nmap_timing <= 3 then diff --git a/scripts/http-date.nse b/scripts/http-date.nse new file mode 100644 index 000000000..49474eba9 --- /dev/null +++ b/scripts/http-date.nse @@ -0,0 +1,80 @@ +description = [[ +Gets the date from HTTP-like services. Also prints how much the date +differs from local time. Local time is the time the HTTP request was +sent, so the difference includes at least the duration of one RTT. +]] + +--- +-- @output +-- 80/tcp open http +-- |_ http-date: Mon, 13 Jul 2009 20:53:46 GMT; -5s from local time. + +author = "David Fifield " + +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" + +categories = {"discovery", "safe"} + +require("http") +require("shortport") + +portrule = shortport.port_or_service({80, 443, 631, 8080}, + {"http", "https", "ipp", "http-alt"}) + +-- Turn a positive or negative number of seconds into a string in one of the +-- forms: +-- 0s +-- +2s +-- -4s +-- +02m38s +-- -9h12m34s +-- 5d17h05m06s +local function format_difftime(t) + local s, sign, sec + + if t > 0 then + sign = "+" + elseif t < 0 then + sign = "-" + else + sign = "" + end + t = math.abs(t) + + -- Seconds. + sec = t % 60 + s = string.format("%gs", sec) + t = math.floor(t / 60) + if t == 0 then return sign .. s end + -- Minutes. + s = string.format("%02dm%02ds", t % 60, sec) + t = math.floor(t / 60) + if t == 0 then return sign .. s end + -- Hours. + s = string.format("%dh", t % 24) .. s + t = math.floor(t / 24) + if t == 0 then return sign .. s end + -- Days. + s = string.format("%dd", t) .. s + return sign .. s +end + +action = function(host, port) + -- Get local time in UTC. + local request_time = os.time(os.date("!*t")) + local response = http.get(host, port, "/") + if not response.status or not response.header["date"] then + return + end + + local date = http.parse_date(response.header["date"]) + if not date then + return + end + + -- Should account for estimated RTT too. + local diff = os.difftime(os.time(date), request_time) + + return string.format("%s; %s from local time.", + response.header["date"], format_difftime(diff)) +end