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