diff options
Diffstat (limited to 'scripts/http-traceroute.nse')
-rw-r--r-- | scripts/http-traceroute.nse | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/scripts/http-traceroute.nse b/scripts/http-traceroute.nse new file mode 100644 index 0000000..39a3506 --- /dev/null +++ b/scripts/http-traceroute.nse @@ -0,0 +1,180 @@ +local http = require "http" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" +local table = require "table" + +description = [[ +Exploits the Max-Forwards HTTP header to detect the presence of reverse proxies. + +The script works by sending HTTP requests with values of the Max-Forwards HTTP +header varying from 0 to 2 and checking for any anomalies in certain response +values such as the status code, Server, Content-Type and Content-Length HTTP +headers and body values such as the HTML title. + +Based on the work of: +* Nicolas Gregoire (nicolas.gregoire@agarri.fr) +* Julien Cayssol (tools@aqwz.com) + +For more information, see: +* http://www.agarri.fr/kom/archives/2011/11/12/traceroute-like_http_scanner/index.html +]] + +--- +-- @args http-traceroute.path The path to send requests to. Defaults to <code>/</code>. +-- @args http-traceroute.method HTTP request method to use. Defaults to <code>GET</code>. +-- Among other values, TRACE is probably the most interesting. +-- +-- @usage +-- nmap --script=http-traceroute <targets> +-- +--@output +-- PORT STATE SERVICE REASON +-- 80/tcp open http syn-ack +-- | http-traceroute: +-- | HTML title +-- | Hop #1: Twitter / Over capacity +-- | Hop #2: t.co / Twitter +-- | Hop #3: t.co / Twitter +-- | Status Code +-- | Hop #1: 502 +-- | Hop #2: 200 +-- | Hop #3: 200 +-- | server +-- | Hop #1: Apache +-- | Hop #2: hi +-- | Hop #3: hi +-- | content-type +-- | Hop #1: text/html; charset=UTF-8 +-- | Hop #2: text/html; charset=utf-8 +-- | Hop #3: text/html; charset=utf-8 +-- | content-length +-- | Hop #1: 4833 +-- | Hop #2: 3280 +-- | Hop #3: 3280 +-- | last-modified +-- | Hop #1: Thu, 05 Apr 2012 00:19:40 GMT +-- | Hop #2 +-- |_ Hop #3 + +author = "Hani Benhabiles" + +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" + +categories = {"discovery", "safe"} + + +portrule = shortport.service("http") + +--- Attempts to extract the html title +-- from an HTTP response body. +--@param responsebody Response's body. +local function extract_title (responsebody) + return responsebody:match "<title>(.-)</title>" +end + +--- Attempts to extract the X-Forwarded-For header +-- from an HTTP response body in case of TRACE requests. +--@param responsebody Response's body. +local function extract_xfwd (responsebody) + return responsebody:match "X-Forwarded-For: [^\r\n]*" +end + +--- Check for differences in response headers, status code +-- and html title between responses. +--@param responses Responses to compare. +--@param method Used HTTP method. +local compare_responses = function(responses, method) + local response, key + local results = {} + local result = {} + local titles = {} + local interesting_headers = { + 'server', + 'via', + 'x-via', + 'x-forwarded-for', + 'content-type', + 'content-length', + 'last-modified', + 'location', + } + + -- Check page title + for key,response in pairs(responses) do + titles[key] = extract_title(response.body) + end + if titles[1] ~= titles[2] or + titles[1] ~= titles[3] then + + table.insert(results, 'HTML title') + for key,response in pairs(responses) do + table.insert(result, "Hop #" .. key .. ": " .. titles[key]) + end + table.insert(results, result) + end + + -- Check status code + if responses[1].status == 502 or + responses[1].status == 483 or + responses[1].status ~= responses[2].status or + responses[1].status ~= responses[3].status then + + result = {} + table.insert(results, 'Status Code') + for key,response in pairs(responses) do + table.insert(result, "Hop #" .. key .. ": " .. tostring(response.status)) + end + table.insert(results, result) + end + + -- Check headers + for _,header in pairs(interesting_headers) do + -- Compare header of different responses + if responses[1].header[header] ~= responses[2].header[header] or + responses[1].header[header] ~= responses[3].header[header] then + + result = {} + table.insert(results, header) + for key,response in pairs(responses) do + if response.header[header] ~= nil then + table.insert(result, "Hop #" .. key .. ": " .. tostring(response.header[header])) + else + table.insert(result, "Hop #" .. key) + end + end + table.insert(results, result) + end + end + + -- Check for X-Forwarded-For in the response body + -- when using TRACE method + if method == "TRACE" then + local xfwd = extract_xfwd(responses[1].body) + if xfwd ~= nil then + table.insert(results, xfwd) + end + end + + return results +end + +action = function(host, port) + local path = stdnse.get_script_args(SCRIPT_NAME .. '.path') or "/" + local method = stdnse.get_script_args(SCRIPT_NAME .. '.method') or "GET" + local responses = {} + local detected = "Possible reverse proxy detected." + + for i = 0,2 do + local response = http.generic_request(host, port, method, path, { ['header'] = { ['Max-Forwards'] = i }, ['no_cache'] = true}) + table.insert(responses, response) + end + + -- Check results + local results = compare_responses(responses, method) + if results ~= nil and nmap.verbosity() == 1 then + return stdnse.format_output(true,detected) + else + return stdnse.format_output(true,results) + end +end |