local http = require "http" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local table = require "table" 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 Host 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 http-vhosts.domain argument or deduced from the target's name. For example when scanning www.example.com, various names of the form .example.com are tried. ]] --- -- @usage -- nmap --script http-vhosts -p 80,8080,443 -- @arg http-vhosts.domain The domain that hostnames will be prepended to, for -- example example.com 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 /. -- @arg http-vhosts.collapse The limit to start collapsing results by status code. Default 20 -- @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 -- -- @todo feature: move hostnames to an external file and allow the user to use another one -- @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 http://nmap.org/book/man-legal.html" categories = { "discovery", "intrusive" } -- List of domains to try. (Will become names like example.com, -- abbot.example.com, admin.example.com, etc.) The list is derived from -- Wikipedia lists of software with a web interface. local HOSTNAMES = { "", "abbot", "admin", "adserver", "alpha", "api", "aptest", "arch", "artifactory", "assembla", "atd", "athena", "atollon", "attask", "attix", "attix5", "automatedqa", "backend", "backup", "bacula", "badboy", "basecamp", "bazaar", "beta", "bitkeeper", "bkp", "branch", "brightwork", "broadwave", "bromine", "bugtracker", "bugzilla", "build", "businessdriver", "campus", "catchlimited", "ccc", "centraldesktop", "cerebro", "civicrm", "clarizen", "clearcase", "clearquest", "clif", "clockingit", "codebeamer", "codendi", "codesourcery", "codeville", "collabtive", "compuware", "concordion", "conformiq", "cppunit", "crm", "cruisecontrol", "cubictest", "cucumber", "cunit", "cvs", "cvsnt", "darcs", "dartenium", "dcvs", "debbugs", "dev", "devel", "development", "devtest", "dieseltest", "digitaltester", "distract", "dolibarr", "dotproject", "dune", "durable", "duxqa", "dynamics", "easy", "egroupware", "eload", "elvior", "empirix", "endeavour", "enterprise", "epesi", "epesibim", "etester", "eventum", "fasttrack", "feng", "firefly", "flumotion", "flyspray", "fogbugz", "foro", "forum", "fossil", "frankenstein", "freecast", "froglogic", "frontend", "ftp", "functional", "functionaltester", "fwptt", "game", "games", "gamma", "gemini", "geniesys", "genietcms", "genius", "git", "glasscubes", "gnats", "goplan", "grinder", "guitar", "gurock", "hammerhead", "hammerora", "harvest", "helix", "help", "helpdesk", "home", "htmlunit", "httpunit", "huddle", "hudson", "hyperoffice", "icecast", "ikiwiki", "images", "incisif", "inflectra", "info", "informup", "intra", "intranet", "issuenet", "isupport", "it", "itcampus", "jabber", "jadeliquid", "jbehave", "jboss", "jcrawler", "jemmy", "jfunc", "jira", "jite", "jmeter", "jotbug", "journyx", "jtest", "jtrack", "junit", "jwebunit", "kayako", "kforge", "kkoop", "launchpad", "liberum", "libresource", "liquidplanner", "liquidtest", "list", "lista", "listas", "listman", "lists", "loadrunner", "magnetic", "mail", "mailman", "mantis", "mantisbt", "manual", "marathon", "matchware", "maven", "mbt", "media", "mercurial", "mercury", "merlin", "messagemagic", "mingle", "mks", "mksintegrity", "mojo", "monotone", "mvn", "nuevosoft", "objentis", "opengoo", "opengroup", "openload", "openproj", "openqa", "opensta", "openwebload", "optimaltest", "orcanos", "origsoft", "otmgr", "otrs", "passmark", "peercast", "perforce", "performancetester", "phpgroupware", "phprojekt", "phpunit", "pivotal", "pjsip", "planisware", "plastic", "postfix", "practitest", "primavera", "principal", "prod", "project", "projecthq", "projectpier", "projectplace", "projectspaces", "projektron", "projistics", "psnext", "pureagent", "pureload", "puretest", "pylot", "qadirector", "qaliber", "qaload", "qamanager", "qatraq", "qmetry", "qmtest", "qpack", "qtest", "qtronic", "qualify", "quickbase", "quicktest", "quicktestpro", "quotium", "rcs", "realese", "redmine", "remedy", "request", "research", "robot", "roundup", "rth", "s3", "sahi", "salome", "sap", "scarab", "sccs", "seam", "seapine", "search", "selenium", "sendmail", "services", "severa", "sharpforge", "shoutcast", "siebel", "silk", "silkcentral", "silkperformer", "simpletest", "simpletestmanagement", "simpleticket", "simulator", "sipp", "sipr", "smartesoft", "smartload", "smartqm", "smartscript", "smartsheet", "soap", "soapui", "software", "softwareresearch", "sourcesafe", "specflow", "spiceworks", "spiratest", "spring", "squish", "staff", "stage", "stagging", "static", "storytestiq", "streaming", "stub", "sugar", "sugarcrm", "supportworks", "svk", "svn", "synergy", "tag", "team", "teamcenter", "teamware", "teamwork", "teamworkpm", "techexcel", "telerik", "tenrox", "test", "test1", "test2", "testbench", "testcase", "testcomplete", "testdirector", "testdrive", "tester", "testing", "testitools", "testlink", "testlog", "testman", "testmanager", "testmaster", "testmasters", "testopia", "testoptimal", "testpartner", "testrail", "testrun", "testsuite", "testtrack", "testuff", "testup", "testworks", "texttest", "tigris", "tomcat", "tplan", "trac", "track", "tracker", "trackersuite", "tricentis", "trunk", "twist", "ubidesk", "unawave", "unreal", "utest", "vault", "verisium", "vnc", "vncrobot", "vperformer", "vpmi", "vtest", "watin", "watir", "web", "web2project", "web2test", "webaii", "webdriver", "webking", "webload", "webspoc", "wiki", "windmill", "winrunner", "wit", "workbook", "workengine", "worklenz", "workspace", "wowza", "wrike", "ws", "www", "www2", "xhtmlunit", "xml-simulator", "xplanner", "xqual", "xstudio", "youtrack", "zentrack", "zephyr", "zoho" } -- uncomment and modify this for shorter scans -- local HOSTNAMES = { -- "", -- "www", -- "docs", -- "images" -- } -- Defines domain to use, first from user and then from host defineDomain = function(host) if stdnse.get_script_args("http-vhosts.domain") then return stdnse.get_script_args("http-vhosts.domain") end 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 domain == "" then return nil end if name == "" then return domain end if domain == "" then return name end return name .. "." .. domain end --- -- Collapses a result -- key -> table -- @param result table -- @return string local collapse = function(result) local collapsed = {""} local limit = tonumber(stdnse.get_script_args("http-vhosts.collapse")) or 10 for code, group in next, result do if #group > limit then collapsed[#collapsed + 1] = #group .. " names had status " .. code else for _,name in ipairs(group) do collapsed[#collapsed + 1] = name end end end return table.concat(collapsed,"\n") end portrule = shortport.http --- -- Script action -- @param host table -- @param port table action = function(host, port) local service = "http" local domain = defineDomain(host) local path = stdnse.get_script_args("http-vhosts.path") or "/" local result = {} for _,name in ipairs(HOSTNAMES) do local http_response local targetname targetname = makeTargetName(name , domain) if targetname ~= nil then http_response = http.head(host, port, path, {header={Host=targetname}, bypass_cache=true, redirect_ok = false}) if not http_response.status then if not http_response["ERROR"] then result["ERROR"]={} end result["ERROR"][#result["ERROR"] + 1] = targetname else local status = tostring(http_response.status) if not result[status] then result[status]={} end if 300 <= http_response.status and http_response.status < 400 then result[status][#result[status] + 1] = targetname .. " : " .. status .. " -> " .. (http_response.header.location or "(no Location provided)") else result[status][#result[status] + 1] = targetname .. " : " .. status end end end end return collapse(result) end