mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
[NSE] Added http-devframework (along with its fingerprints file) that tries to find out the technology behind the target website.
This commit is contained in:
363
nselib/data/http-devframework-fingerprints.lua
Normal file
363
nselib/data/http-devframework-fingerprints.lua
Normal file
@@ -0,0 +1,363 @@
|
||||
local http = require "http"
|
||||
local table = require "table"
|
||||
local url = require "url"
|
||||
|
||||
---
|
||||
-- http-devframework-fingerprints.lua
|
||||
-- This file contains fingerprint data for http-devframework.nse
|
||||
--
|
||||
-- STRUCTURE:
|
||||
-- * <code>name</code> - Descriptive name
|
||||
-- * <code>rapidDetect</code> - Callback function that is called in the beginning
|
||||
-- of detection process. It takes the host and port of the target website as
|
||||
-- arguments.
|
||||
-- * <code>consumingDetect</code> - Callback function that is called for each
|
||||
-- spidered page. It takes the body of the response (HTML source code) and the
|
||||
-- requested path as arguments.
|
||||
---
|
||||
|
||||
|
||||
tools = { Django = { rapidDetect = function(host, port)
|
||||
|
||||
-- Check if the site gives that familiar Django admin login page.
|
||||
response = http.get(host, port, "/admin/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "Log in | Django site admin") or
|
||||
string.find(response.body, "this_is_the_login_form") or
|
||||
string.find(response.body, "csrfmiddlewaretoken") then
|
||||
return "Django detected. Found Django admin login page on /admin/"
|
||||
end
|
||||
end
|
||||
|
||||
-- In Django, the cookie sessionid is being set when you log in
|
||||
-- and forms will probably set a cookie called csrftoken.
|
||||
if response.cookies then
|
||||
for _, c in pairs(response.cookies) do
|
||||
if c.name == "csrftoken" then
|
||||
return "Django detected. Found sessionid cookie which means the contrib.auth package for authentication is enabled."
|
||||
elseif c.name == "sessionid" then
|
||||
return "Django detected. Found csrftoken cookie."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- See if DEBUG mode still happens to be true.
|
||||
response = http.get(host, port, "/random404page/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "<code>DEBUG = True</code>") then
|
||||
return "Django detected. Found Django error page on /random404page/"
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
if page then
|
||||
if string.find(page, "csrfmiddlewaretoken") then
|
||||
return "Django detected. Found csrfmiddlewaretoken on " .. path
|
||||
end
|
||||
if string.find(page, "id=\"id_") then
|
||||
return "Django detected. Found id_ preffix in id attribute name on " .. path
|
||||
end
|
||||
if string.find(page, "%-TOTAL%-FORMS") or string.find(page, "%-DELETE") then
|
||||
return "Django detected. Found -TOTAL-FORMS and -DELETE hidden inputs, which means there is a Django formset on " .. path
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
RubyOnRails = { rapidDetect = function(host, port)
|
||||
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
-- Check for Mongrel or Passenger in the "Server" or "X-Powered-By" header
|
||||
for h, v in pairs(response.header) do
|
||||
if h == "x-powered-by" or h == "server" then
|
||||
local vl = v:lower()
|
||||
local m = vl:match("mongrel") or vl:match("passenger")
|
||||
if m then
|
||||
return "RoR detected. Found '" .. m .. "' in " .. h .. " header sent by the server."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- /rails/info/propertires shows project info when in development mode
|
||||
response = http.get(host, port, "/rails/info/properties")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "Ruby version") then
|
||||
return "RoR detected. Found properties file on /rails/info/properties/"
|
||||
end
|
||||
end
|
||||
|
||||
-- Make up a bad path and match the error page
|
||||
response = http.get(host, port, "/random404page/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "Routing Error") then
|
||||
return "RoR detected. Found RoR routing error page on /random404page/"
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
|
||||
-- Check the source and look for csrf patterns.
|
||||
if page then
|
||||
if string.find(page, "csrf%-param") or string.find(page, "csrf%-token") then
|
||||
return "RoR detected. Found csrf field on" .. path
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
},
|
||||
|
||||
|
||||
ASPdotNET = { rapidDetect = function(host, port)
|
||||
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
-- Look for an ASP.NET header.
|
||||
for h, v in pairs(response.header) do
|
||||
vl = v:lower()
|
||||
if h == "x-aspnet-version" or string.find(vl, "asp") then
|
||||
return "ASP.NET detected. Found related header."
|
||||
end
|
||||
end
|
||||
|
||||
if response.cookies then
|
||||
for _, c in pairs(response.cookies) do
|
||||
if c.name == "aspnetsessionid" then
|
||||
return "ASP.NET detected. Found aspnetsessionid cookie."
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
-- Check the source and look for common traces.
|
||||
if page then
|
||||
if string.find(page, " __VIEWSTATE") or
|
||||
string.find(page, "__EVENT") or
|
||||
string.find(page, "__doPostBack") or
|
||||
string.find(page, "aspnetForm") or
|
||||
string.find(page, "ctl00_") then
|
||||
return "ASP.NET detected. Found common traces on" .. path
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
CodeIgniter = { rapidDetect = function(host, port)
|
||||
|
||||
-- Match default error page.
|
||||
response = http.get(host, port, "/random404page/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "#990000") and
|
||||
string.find(response.body, "404 Page Not Found") then
|
||||
return "CodeIgniter detected. Found CodeIgniter default error page on /random404page/"
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
|
||||
CakePHP = { rapidDetect = function(host, port)
|
||||
|
||||
|
||||
-- Find CAKEPHP header.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
for h, v in pairs(response.header) do
|
||||
vl = v:lower()
|
||||
if string.find(vl, "cakephp") then
|
||||
return "CakePHP detected. Found related header."
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
|
||||
Symfony = { rapidDetect = function(host, port)
|
||||
|
||||
-- Find Symfony header.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
for h, v in pairs(response.header) do
|
||||
vl = v:lower()
|
||||
if string.find(vl, "symfony") then
|
||||
return "Symfony detected. Found related header."
|
||||
end
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
|
||||
Wordpress = { rapidDetect = function(host, port)
|
||||
|
||||
-- Check for common traces in the source code.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "content=[\"']WordPress") or
|
||||
string.find(response.body, "wp%-content") then
|
||||
return "Wordpress detected. Found common traces on /"
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the default login page exists.
|
||||
response = http.get(host, port, "/wp%-login")
|
||||
|
||||
if response.status == "200" then
|
||||
return "Wordpress detected. Found WP login page on /wp-login"
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
if page then
|
||||
if string.find(page, "content=[\"']WordPress") or
|
||||
string.find(page, "wp-content") then
|
||||
return "Wordpress detected. Found common traces on " .. page
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
Joomla = { rapidDetect = function(host, port)
|
||||
|
||||
|
||||
-- Check for common traces in the source code.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "content=[\"']Joomla!") then
|
||||
return "Joomla detected. Found common traces on /"
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if the default login page exists.
|
||||
response = http.get(host, port, "/administrator")
|
||||
|
||||
if response.body and string.find(response.body, "Joomla") then
|
||||
return "Joomla detected. Found Joomla login page on /administrator/"
|
||||
end
|
||||
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
if page and string.find(page, "content=[\"']Joomla!") then
|
||||
return "Joomla detected. Found common traces on " .. page
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
Drupal = { rapidDetect = function(host, port)
|
||||
|
||||
-- Check for common traces in the source code.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "content=[\"']Drupal") then
|
||||
return "Drupal detected. Found common traces on /"
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
if page and string.find(page, "content=[\"']Drupal") then
|
||||
return "Drupal detected. Found common traces on " .. page
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
MediaWiki = { rapidDetect = function(host, port)
|
||||
|
||||
-- Check for common traces in the source code.
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.body then
|
||||
if string.find(response.body, "content=[\"']MediaWiki") or
|
||||
string.find(response.body, "/mediawiki/") then
|
||||
return "MediaWiki detected. Found common traces on /"
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
if page and string.find(page, "content=[\"']MediaWiki") or
|
||||
string.find(page, "/mediawiki/") then
|
||||
return "MediaWiki detected. Found common traces on " .. page
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
ColdFusion = { rapidDetect = function(host, port)
|
||||
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.cookies then
|
||||
for _, c in pairs(response.cookies) do
|
||||
if c.name == "cfid" or c.name == "cftoken" then
|
||||
return "ColdFusion detected. Found " .. c.name .. " cookie."
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
|
||||
Broadvision = { rapidDetect = function(host, port)
|
||||
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.cookies then
|
||||
for _, c in pairs(response.cookies) do
|
||||
if string.find(c.name, "bv_") then
|
||||
return "Broadvision detected. Found " .. c.name .. " cookie."
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
|
||||
WebSphereCommerce = { rapidDetect = function(host, port)
|
||||
|
||||
response = http.get(host, port, "/")
|
||||
|
||||
if response.cookies then
|
||||
for _, c in pairs(response.cookies) do
|
||||
if string.find(c.name, "wc_") then
|
||||
return "WebSphere Commerce detected. Found " .. c.name .. " cookie."
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
consumingDetect = function(page, path)
|
||||
return
|
||||
end
|
||||
},
|
||||
}
|
||||
145
scripts/http-devframework.nse
Normal file
145
scripts/http-devframework.nse
Normal file
@@ -0,0 +1,145 @@
|
||||
description = [[
|
||||
|
||||
Tries to find out the technology behind the target website.
|
||||
|
||||
The script checks for certain defaults that might not have been changed, like
|
||||
common headers or URLs or HTML content.
|
||||
|
||||
While the script does some guessing, note that overall there's no way to
|
||||
determine what technologies a given site is using.
|
||||
|
||||
You can help improve this script by adding new entries to
|
||||
nselib/data/http-tools-fingerprints.lua
|
||||
|
||||
Each entry must have:
|
||||
* <code>rapidDetect</code> - Callback function that is called in the beginning
|
||||
of detection process. It takes the host and port of target website as arguments.
|
||||
* <code>consumingDetect</code> - Callback function that is called for each
|
||||
spidered page. It takes the body of the response (HTML code) and the requested
|
||||
path as arguments.
|
||||
|
||||
Note that the <code>consumingDetect</code> callback will not take place only if
|
||||
<code>rapid</code> option is enabled.
|
||||
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap -p80 --script http-devframework.nse <target>
|
||||
--
|
||||
-- @args http-errors.rapid boolean value that determines if a rapid detection
|
||||
-- should take place. The main difference of a rapid vs a lengthy detection
|
||||
-- is that second one requires crawling through the website. Default: false
|
||||
-- (lengthy detection is performed)
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE REASON
|
||||
-- 80/tcp open http syn-ack
|
||||
-- |_http-devframework: Django detected. Found Django admin login page on /admin/
|
||||
---
|
||||
|
||||
categories = {"discovery", "intrusive"}
|
||||
author = "George Chatzisofroniou"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
local http = require "http"
|
||||
local shortport = require "shortport"
|
||||
local stdnse = require "stdnse"
|
||||
local table = require "table"
|
||||
local string = require "string"
|
||||
local httpspider = require "httpspider"
|
||||
|
||||
portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open")
|
||||
|
||||
local function loadFingerprints(filename, cat)
|
||||
|
||||
local file, fingerprints
|
||||
|
||||
-- Find the file
|
||||
filename = nmap.fetchfile('nselib/data/' .. filename) or filename
|
||||
|
||||
-- Load the file
|
||||
stdnse.print_debug(1, "%s: Loading fingerprints: %s", SCRIPT_NAME, filename)
|
||||
local env = setmetatable({fingerprints = {}}, {__index = _G});
|
||||
file = loadfile(filename, "t", env)
|
||||
|
||||
if( not(file) ) then
|
||||
stdnse.print_debug(1, "%s: Couldn't load the file: %s", SCRIPT_NAME, filename)
|
||||
return
|
||||
end
|
||||
|
||||
file()
|
||||
fingerprints = env.tools
|
||||
|
||||
return fingerprints
|
||||
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
|
||||
local tools = stdnse.get_script_args("http-devframework.fingerprintfile") or loadFingerprints("nselib/data/http-devframework-fingerprints.lua")
|
||||
local rapid = stdnse.get_script_args("http-devframework.rapid")
|
||||
|
||||
local d
|
||||
|
||||
-- Run rapidDetect() callbacks.
|
||||
for f, method in pairs(tools) do
|
||||
d = method["rapidDetect"](host, port)
|
||||
if d then
|
||||
return d
|
||||
end
|
||||
end
|
||||
|
||||
local crawler = httpspider.Crawler:new(host, port, '/', { scriptname = SCRIPT_NAME,
|
||||
maxpagecount = 40,
|
||||
maxdepth = -1,
|
||||
withinhost = 1
|
||||
})
|
||||
|
||||
if rapid then
|
||||
return "Couldn't determine the underlying framework or CMS. Try turning off 'rapid' mode."
|
||||
end
|
||||
|
||||
crawler.options.doscraping = function(url)
|
||||
if crawler:iswithinhost(url)
|
||||
and not crawler:isresource(url, "js")
|
||||
and not crawler:isresource(url, "css") then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
crawler:set_timeout(10000)
|
||||
|
||||
while (true) do
|
||||
|
||||
local response, path
|
||||
|
||||
status, r = crawler:crawl()
|
||||
-- if the crawler fails it can be due to a number of different reasons
|
||||
-- most of them are "legitimate" and should not be reason to abort
|
||||
if (not(status)) then
|
||||
if (r.err) then
|
||||
return stdnse.format_output(true, ("ERROR: %s"):format(r.reason))
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
response = r.response
|
||||
path = tostring(r.url)
|
||||
|
||||
if (response.body) then
|
||||
|
||||
-- Run consumingDetect() callbacks.
|
||||
for f, method in pairs(tools) do
|
||||
d = method["consumingDetect"](response.body, path)
|
||||
if d then
|
||||
return d
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return "Couldn't determine the underlying framework or CMS. Try increasing 'httpspider.maxpagecount' value to spider more pages."
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user