186 lines
5.7 KiB
Lua
186 lines
5.7 KiB
Lua
local coroutine = require "coroutine"
|
|
local http = require "http"
|
|
local io = require "io"
|
|
local nmap = require "nmap"
|
|
local shortport = require "shortport"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local datafiles = require "datafiles"
|
|
|
|
description = [[
|
|
Searches for web virtual hostnames by making a large number of HEAD requests against http servers using common hostnames.
|
|
|
|
Each HEAD request provides a different
|
|
<code>Host</code> header. The hostnames come from a built-in default
|
|
list. Shows the names that return a document. Also shows the location of
|
|
redirections.
|
|
|
|
The domain can be given as the <code>http-vhosts.domain</code> argument or
|
|
deduced from the target's name. For example when scanning www.example.com,
|
|
various names of the form <name>.example.com are tried.
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- nmap --script http-vhosts -p 80,8080,443 <target>
|
|
--
|
|
-- @arg http-vhosts.domain The domain that hostnames will be prepended to, for
|
|
-- example <code>example.com</code> yields www.example.com, www2.example.com,
|
|
-- etc. If not provided, a guess is made based on the hostname.
|
|
-- @arg http-vhosts.path The path to try to retrieve. Default <code>/</code>.
|
|
-- @arg http-vhosts.collapse The limit to start collapsing results by status code. Default <code>20</code>
|
|
-- @arg http-vhosts.filelist file with the vhosts to try. Default <code>nselib/data/vhosts-default.lst</code>
|
|
--
|
|
-- @output
|
|
-- PORT STATE SERVICE REASON
|
|
-- 80/tcp open http syn-ack
|
|
-- | http-vhosts:
|
|
-- | example.com: 301 -> http://www.example.com/
|
|
-- | www.example.com: 200
|
|
-- | docs.example.com: 302 -> https://www.example.com/docs/
|
|
-- |_images.example.com: 200
|
|
--
|
|
-- @internal: see http://seclists.org/nmap-dev/2010/q4/401 and http://seclists.org/nmap-dev/2010/q4/445
|
|
--
|
|
--
|
|
-- @todo feature: add option report and implement it
|
|
-- @internal after stripping sensitive info like ip, domain names, hostnames
|
|
-- and redirection targets from the result, append it to a file
|
|
-- that can then be uploaded. If enough info is gathered, the names
|
|
-- will be weighted. It can be shared with metasploit
|
|
--
|
|
-- @todo feature: fill nsedoc
|
|
--
|
|
-- @todo feature: register results for other scripts (external help needed)
|
|
--
|
|
-- @todo feature: grow names list (external help needed)
|
|
--
|
|
|
|
author = "Carlos Pantelides"
|
|
|
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
|
|
|
categories = { "discovery", "intrusive" }
|
|
|
|
local arg_domain = stdnse.get_script_args(SCRIPT_NAME..".domain")
|
|
local arg_path = stdnse.get_script_args(SCRIPT_NAME..".path") or "/"
|
|
local arg_filelist = stdnse.get_script_args(SCRIPT_NAME..'.filelist')
|
|
local arg_collapse = tonumber(stdnse.get_script_args(SCRIPT_NAME..".collapse")) or 10
|
|
|
|
-- Defines domain to use, first from user and then from host
|
|
local defineDomain = function(host)
|
|
local name = stdnse.get_hostname(host)
|
|
if name and name ~= host.ip then
|
|
local pos = string.find (name, ".",1,true)
|
|
if not pos then return name end
|
|
return string.sub (name, pos + 1)
|
|
end
|
|
end
|
|
|
|
---
|
|
-- Makes a target name with a name and a domain
|
|
-- @param name string
|
|
-- @param domain string
|
|
-- @return string
|
|
local makeTargetName = function(name,domain)
|
|
if name and name ~= "" then
|
|
if domain and domain ~= "" then
|
|
return name .. "." .. domain
|
|
else
|
|
return name
|
|
end
|
|
elseif domain and domain ~= "" then
|
|
return domain
|
|
end
|
|
end
|
|
|
|
|
|
---
|
|
-- Collapses a result
|
|
-- key -> table
|
|
-- @param result table
|
|
-- @return string
|
|
local collapse = function(result)
|
|
local collapsed = {""}
|
|
for code, group in next, result do
|
|
if #group > arg_collapse then
|
|
table.insert(collapsed, ("%d names had status %s"):format(#group, code))
|
|
else
|
|
for _,name in ipairs(group) do
|
|
table.insert(collapsed, name)
|
|
end
|
|
end
|
|
end
|
|
return table.concat(collapsed,"\n")
|
|
end
|
|
|
|
local testThread = function(result, host, port, name)
|
|
local condvar = nmap.condvar(result)
|
|
local targetname = makeTargetName(name , arg_domain)
|
|
if targetname ~= nil then
|
|
local http_response = http.generic_request(host, port, "HEAD", arg_path, {header={Host=targetname}})
|
|
|
|
if not http_response.status then
|
|
result["ERROR"] = result["ERROR"] or {}
|
|
table.insert(result["ERROR"], targetname)
|
|
else
|
|
local status = tostring(http_response.status)
|
|
result[status] = result[status] or {}
|
|
if ( 300 <= http_response.status and http_response.status < 400 ) then
|
|
table.insert(result[status], ("%s : %s -> %s"):format(targetname, status, (http_response.header.location or "(no Location provided)")))
|
|
else
|
|
table.insert(result[status], ("%s : %s"):format(targetname, status))
|
|
end
|
|
end
|
|
end
|
|
condvar "signal"
|
|
end
|
|
|
|
local readFromFile = function(filename)
|
|
local database = {}
|
|
for l in io.lines(filename) do
|
|
table.insert(database, l)
|
|
end
|
|
return database
|
|
end
|
|
|
|
portrule = shortport.http
|
|
|
|
---
|
|
-- Script action
|
|
-- @param host table
|
|
-- @param port table
|
|
action = function(host, port)
|
|
local result, threads, hostnames = {}, {}, {}
|
|
local condvar = nmap.condvar(result)
|
|
local status
|
|
|
|
if arg_filelist then
|
|
hostnames = readFromFile(arg_filelist)
|
|
else
|
|
status, hostnames = datafiles.parse_file("nselib/data/vhosts-default.lst" , {})
|
|
if not status then
|
|
stdnse.debug1("Can not open file with vhosts file names list")
|
|
return
|
|
end
|
|
end
|
|
|
|
arg_domain = arg_domain or defineDomain(host)
|
|
for _,name in ipairs(hostnames) do
|
|
local co = stdnse.new_thread(testThread, result, host, port, name)
|
|
threads[co] = true
|
|
end
|
|
|
|
while(next(threads)) do
|
|
for t in pairs(threads) do
|
|
threads[t] = ( coroutine.status(t) ~= "dead" ) and true or nil
|
|
end
|
|
if ( next(threads) ) then
|
|
condvar "wait"
|
|
end
|
|
end
|
|
|
|
return collapse(result)
|
|
end
|