mirror of
https://github.com/nmap/nmap.git
synced 2025-12-08 21:51:28 +00:00
Added http-traceroute script which exploits Max-Forwards HTTP header to detect reverse proxies.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added the script http-traceroute, which exploits the
|
||||
Max-Forwards HTTP header to detect reverse proxies. [Hani Benhabiles]
|
||||
|
||||
o Added the script distcc-CVE-2004-2687 that checks and exploits a remote
|
||||
command execution vulnerability in distcc. [Patrik Karlsson]
|
||||
|
||||
|
||||
190
scripts/http-traceroute.nse
Normal file
190
scripts/http-traceroute.nse
Normal file
@@ -0,0 +1,190 @@
|
||||
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 --script-args http-traceroute.verbosity=1 <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 http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = {"discovery", "safe", "default"}
|
||||
|
||||
require 'http'
|
||||
require 'stdnse'
|
||||
require 'shortport'
|
||||
|
||||
portrule = shortport.service("http")
|
||||
|
||||
--- Attempts to extract the html title
|
||||
-- from an HTTP response body.
|
||||
--@param responsebody Response's body.
|
||||
local extract_title = function(responsebody)
|
||||
local title = ''
|
||||
local titlere = '<title>(?P<title>.*)</title>'
|
||||
local regex = pcre.new(titlere, 0, "C")
|
||||
local limit, limit2, matches = regex:match(responsebody)
|
||||
if limit ~= nil then
|
||||
title = matches["title"]
|
||||
end
|
||||
return 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 extract_xfwd = function(responsebody)
|
||||
local xfwd = ''
|
||||
local xfwdre = '(?P<xfwd>X-Forwarded-For: .*)'
|
||||
local regex = pcre.new(xfwdre, 0, "C")
|
||||
local limit, limit2, matches = regex:match(responsebody)
|
||||
if limit ~= nil then
|
||||
xfwd = matches["xfwd"]
|
||||
end
|
||||
return xfwd
|
||||
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
|
||||
@@ -165,6 +165,7 @@ Entry { filename = "http-robtex-reverse-ip.nse", categories = { "discovery", "ex
|
||||
Entry { filename = "http-robtex-shared-ns.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "http-title.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "http-trace.nse", categories = { "discovery", "safe", "vuln", } }
|
||||
Entry { filename = "http-traceroute.nse", categories = { "discovery", "safe", "default", } }
|
||||
Entry { filename = "http-unsafe-output-escaping.nse", categories = { "discovery", "intrusive", } }
|
||||
Entry { filename = "http-userdir-enum.nse", categories = { "auth", "intrusive", } }
|
||||
Entry { filename = "http-vhosts.nse", categories = { "discovery", "intrusive", } }
|
||||
|
||||
Reference in New Issue
Block a user