mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
http-drupal-enum replaces http-drupal-modules.
The script now supports drupal theme listing as well. Updated drupal-modules.lst and added drupal-themes.lst.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
1333
nselib/data/drupal-themes.lst
Normal file
1333
nselib/data/drupal-themes.lst
Normal file
File diff suppressed because it is too large
Load Diff
229
scripts/http-drupal-enum.nse
Normal file
229
scripts/http-drupal-enum.nse
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Enumerates the installed Drupal modules/themes by using a list of known modules and themes.
|
||||||
|
|
||||||
|
The script works by iterating over module/theme names and requesting
|
||||||
|
MODULES_PATH/MODULE_NAME/LICENSE.txt for modules and THEME_PATH/THEME_NAME/LOGO.png.
|
||||||
|
MODULES_PATH is either provided by the user, grepped for in the html body
|
||||||
|
or defaulting to sites/all/modules/.
|
||||||
|
|
||||||
|
If the response status code is 200, it means that the module/theme is installed. By
|
||||||
|
default, the script checks for the top 100 modules (by downloads), given the
|
||||||
|
huge number of existing modules (~10k).
|
||||||
|
|
||||||
|
If you want to update your themes or module list refer to.
|
||||||
|
*https://svn.nmap.org/nmap-exp/gyani/misc/drupal-update.py
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @args http-drupal-enum.root The base path. Defaults to <code>/</code>.
|
||||||
|
-- @args http-drupal-enum.number Number of modules to check.
|
||||||
|
-- Use this option with a number or "all" as an argument to test for all modules.
|
||||||
|
-- Defaults to <code>100</code>.
|
||||||
|
-- @args http-drupal-enum.modules_path Direct Path for Modules
|
||||||
|
-- @args http-drupal-enum.themes_path Direct Path for Themes
|
||||||
|
-- @args http-drupal-enum.type default all.choose between "themes" and "modules"
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 80 --script http-drupal-enum --script-args modules_path="sites/all/modules/",themes_path="themes/",number=10 <target>
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-drupal-enum:
|
||||||
|
-- | Themes:
|
||||||
|
-- | adaptivetheme
|
||||||
|
-- | Modules:
|
||||||
|
-- | views
|
||||||
|
-- | token
|
||||||
|
-- | ctools
|
||||||
|
-- | pathauto
|
||||||
|
-- | date
|
||||||
|
-- | imce
|
||||||
|
-- |_ webform
|
||||||
|
--
|
||||||
|
-- Final times for host: srtt: 329644 rttvar: 185712 to: 1072492
|
||||||
|
--
|
||||||
|
-- @xmloutput
|
||||||
|
-- <table key="Themes">
|
||||||
|
-- <elem>adaptivetheme</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="Modules">
|
||||||
|
-- <elem>views</elem>
|
||||||
|
-- <elem>token</elem>
|
||||||
|
-- <elem>ctools</elem>
|
||||||
|
-- <elem>pathauto</elem>
|
||||||
|
-- <elem>date</elem>
|
||||||
|
-- <elem>imce</elem>
|
||||||
|
-- <elem>webform</elem>
|
||||||
|
-- </table>
|
||||||
|
|
||||||
|
|
||||||
|
author = {
|
||||||
|
"Hani Benhabiles",
|
||||||
|
"Gyanendra Mishra",
|
||||||
|
}
|
||||||
|
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
|
categories = {
|
||||||
|
"discovery",
|
||||||
|
"intrusive",
|
||||||
|
}
|
||||||
|
|
||||||
|
local DEFAULT_SEARCH_LIMIT = 100
|
||||||
|
local DEFAULT_MODULES_PATH = 'sites/all/modules/'
|
||||||
|
local DEFAULT_THEMES_PATH = 'sites/all/themes/'
|
||||||
|
local IDENTIFICATION_STRING = "GNU GENERAL PUBLIC LICENSE"
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
--Reads database
|
||||||
|
local function read_data (file)
|
||||||
|
return coroutine.wrap(function ()
|
||||||
|
for line in file:lines() do
|
||||||
|
if not line:match "^%s*#" and not line:match "^%s*$" then
|
||||||
|
coroutine.yield(line)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--Checks if the module/theme file exists
|
||||||
|
local function assign_file (act_file)
|
||||||
|
if not act_file then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local temp_file = io.open(act_file, "r")
|
||||||
|
if not temp_file then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return temp_file
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Attempts to find modules path
|
||||||
|
local get_path = function (host, port, root, type_of)
|
||||||
|
local default_path
|
||||||
|
if type_of == "themes" then
|
||||||
|
default_path = DEFAULT_THEMES_PATH
|
||||||
|
else
|
||||||
|
default_path = DEFAULT_MODULES_PATH
|
||||||
|
end
|
||||||
|
local body = http.get(host, port, root).body or ""
|
||||||
|
local pattern = "sites/[%w.-/]*/" .. type_of .. "/"
|
||||||
|
local found_path = body:match(pattern)
|
||||||
|
return found_path or default_path
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function action (host, port)
|
||||||
|
local result = stdnse.output_table()
|
||||||
|
local file = {}
|
||||||
|
local all = {}
|
||||||
|
local requests = {}
|
||||||
|
local method = "HEAD"
|
||||||
|
|
||||||
|
--Read script arguments
|
||||||
|
local resource_type = stdnse.get_script_args(SCRIPT_NAME .. ".type") or "all"
|
||||||
|
local root = stdnse.get_script_args(SCRIPT_NAME .. ".root") or "/"
|
||||||
|
local search_limit = stdnse.get_script_args(SCRIPT_NAME .. ".number") or DEFAULT_SEARCH_LIMIT
|
||||||
|
local themes_path = stdnse.get_script_args(SCRIPT_NAME .. ".themes_path")
|
||||||
|
local modules_path = stdnse.get_script_args(SCRIPT_NAME .. ".modules_path")
|
||||||
|
|
||||||
|
local themes_file = nmap.fetchfile "nselib/data/drupal-themes.lst"
|
||||||
|
local modules_file = nmap.fetchfile "nselib/data/drupal-modules.lst"
|
||||||
|
|
||||||
|
if resource_type == "themes" or resource_type == "all" then
|
||||||
|
local theme_db = assign_file(themes_file)
|
||||||
|
if not theme_db then
|
||||||
|
return false, "Couldn't find drupal-themes.lst in /nselib/data/"
|
||||||
|
else
|
||||||
|
file['Themes'] = theme_db
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if resource_type == "modules" or resource_type == "all" then
|
||||||
|
local modules_db = assign_file(modules_file)
|
||||||
|
if not modules_db then
|
||||||
|
return false, "Couldn't find drupal-modules.lst in /nselib/data/"
|
||||||
|
else
|
||||||
|
file['Modules'] = modules_db
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if search_limit == "all" then
|
||||||
|
search_limit = nil
|
||||||
|
else
|
||||||
|
search_limit = tonumber(search_limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not themes_path then
|
||||||
|
themes_path = (root .. get_path(host, port, root, "themes")):gsub("//", "/")
|
||||||
|
end
|
||||||
|
if not modules_path then
|
||||||
|
modules_path = (root .. get_path(host, port, root, "modules")):gsub("//", "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- We default to HEAD requests unless the server returns
|
||||||
|
-- non 404 (200 or other) status code
|
||||||
|
|
||||||
|
local response = http.head(host, port, modules_path .. stdnse.generate_random_string(8) .. "/LICENSE.txt")
|
||||||
|
if response.status ~= 404 then
|
||||||
|
method = "GET"
|
||||||
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(file) do
|
||||||
|
local count = 0
|
||||||
|
for resource_name in read_data(value) do
|
||||||
|
count = count + 1
|
||||||
|
if search_limit and count > search_limit then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
-- add request to pipeline
|
||||||
|
if key == "Modules" then
|
||||||
|
all = http.pipeline_add(modules_path .. resource_name .. "/LICENSE.txt", nil, all, method)
|
||||||
|
else
|
||||||
|
all = http.pipeline_add(themes_path .. resource_name .. "/LICENSE.txt", nil, all, method)
|
||||||
|
end
|
||||||
|
-- add to requests buffer
|
||||||
|
table.insert(requests, resource_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- send requests
|
||||||
|
local pipeline_responses = http.pipeline_go(host, port, all)
|
||||||
|
if not pipeline_responses then
|
||||||
|
stdnse.print_debug(1, "No answers from pipelined requests")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, response in ipairs(pipeline_responses) do
|
||||||
|
-- Module exists if 200 on HEAD
|
||||||
|
-- or contains identification string for GET or key is themes and is image
|
||||||
|
if response.status == 200 and (method == "HEAD" or (method == "GET" and response.body:match(IDENTIFICATION_STRING))) then
|
||||||
|
result[key] = result[key] or {}
|
||||||
|
table.insert(result[key], requests[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
requests = {}
|
||||||
|
all = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
if result['Themes'] or result['Modules'] then
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
if nmap.verbosity() > 1 then
|
||||||
|
return string.format("Nothing found amongst the top %s resources," .. "use --script-args number=<number|all> for deeper analysis)", search_limit)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
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"
|
|
||||||
|
|
||||||
description = [[
|
|
||||||
Enumerates the installed Drupal modules by using a list of known modules.
|
|
||||||
|
|
||||||
The script works by iterating over module names and requesting
|
|
||||||
MODULES_PATH/MODULE_NAME/LICENSE.txt. MODULES_PATH is either provided by the
|
|
||||||
user, grepped for in the html body or defaulting to sites/all/modules/. If the
|
|
||||||
response status code is 200, it means that the module is installed. By
|
|
||||||
default, the script checks for the top 100 modules (by downloads), given the
|
|
||||||
huge number of existing modules (~10k).
|
|
||||||
]]
|
|
||||||
|
|
||||||
---
|
|
||||||
-- @args http-drupal-modules.root The base path. Defaults to <code>/</code>.
|
|
||||||
-- @args http-drupal-modules.number Number of modules to check.
|
|
||||||
-- Use this option with a number or "all" as an argument to test for all modules.
|
|
||||||
-- Defaults to <code>100</code>.
|
|
||||||
-- @args http-drupal-modules.modules_path The path to the modules folder. If not set, the script will try to
|
|
||||||
-- find the path or default to <code>sites/all/modules/</code>
|
|
||||||
--
|
|
||||||
-- @usage
|
|
||||||
-- nmap --script=http-drupal-modules --script-args http-drupal-modules.root="/path/",http-drupal-modules.number=1000 <targets>
|
|
||||||
--
|
|
||||||
--@output
|
|
||||||
-- Interesting ports on my.woot.blog (123.123.123.123):
|
|
||||||
-- PORT STATE SERVICE REASON
|
|
||||||
-- 80/tcp open http syn-ack
|
|
||||||
-- | http-drupal-modules:
|
|
||||||
-- | views
|
|
||||||
-- | token
|
|
||||||
-- | cck
|
|
||||||
-- | pathauto
|
|
||||||
-- | ctools
|
|
||||||
-- | admin_menu
|
|
||||||
-- | imageapi
|
|
||||||
-- | filefield
|
|
||||||
-- | date
|
|
||||||
-- | imagecache
|
|
||||||
-- | imagefield
|
|
||||||
-- | google_analytics
|
|
||||||
-- | webform
|
|
||||||
-- | jquery_ui
|
|
||||||
-- |_ link
|
|
||||||
|
|
||||||
author = "Hani Benhabiles"
|
|
||||||
|
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
||||||
|
|
||||||
categories = {"discovery", "intrusive"}
|
|
||||||
|
|
||||||
|
|
||||||
portrule = shortport.service("http")
|
|
||||||
|
|
||||||
--- Attempts to find modules path
|
|
||||||
--@param host nmap host table
|
|
||||||
--@param port nmap port table
|
|
||||||
--@param root Where to grep for the modules base path
|
|
||||||
local get_modules_path = function(host, port, root)
|
|
||||||
local default_path = "sites/all/modules/"
|
|
||||||
local modules_path = stdnse.get_script_args(SCRIPT_NAME .. '.modules_path')
|
|
||||||
|
|
||||||
if modules_path == nil then
|
|
||||||
local body = http.get(host, port, root).body
|
|
||||||
modules_path = body:match "sites/[%w.-]*/modules/"
|
|
||||||
end
|
|
||||||
return modules_path or default_path
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
action = function(host, port)
|
|
||||||
local root = stdnse.get_script_args(SCRIPT_NAME .. '.root') or "/"
|
|
||||||
local result = stdnse.output_table()
|
|
||||||
local all = {}
|
|
||||||
local requests = {}
|
|
||||||
local count = 0
|
|
||||||
local method = "HEAD"
|
|
||||||
local identification_string = "GNU GENERAL PUBLIC LICENSE"
|
|
||||||
|
|
||||||
-- Default number of modules to be checked.
|
|
||||||
local modules_limit = stdnse.get_script_args(SCRIPT_NAME .. '.number')
|
|
||||||
if modules_limit == 'all' then
|
|
||||||
modules_limit = nil
|
|
||||||
elseif modules_limit == nil then
|
|
||||||
modules_limit = 100
|
|
||||||
else
|
|
||||||
modules_limit = tonumber(modules_limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
local modules_path = get_modules_path(host, port, root)
|
|
||||||
--Check modules list
|
|
||||||
local drupal_modules_list = nmap.fetchfile("nselib/data/drupal-modules.lst")
|
|
||||||
if not drupal_modules_list then
|
|
||||||
return false, "Couldn't find nselib/data/drupal-modules.lst"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- We default to HEAD requests unless the server returns
|
|
||||||
-- non 404 (200 or other) status code
|
|
||||||
local response = http.head(host,port,root .. modules_path .. "randomaBcD/LICENSE.txt")
|
|
||||||
if response.status ~= 404 then
|
|
||||||
method = "GET"
|
|
||||||
end
|
|
||||||
|
|
||||||
for module_name in io.lines(drupal_modules_list) do
|
|
||||||
count = count + 1
|
|
||||||
if modules_limit and count>modules_limit then break end
|
|
||||||
-- add request to pipeline
|
|
||||||
all = http.pipeline_add(root .. modules_path.. module_name .. "/LICENSE.txt",
|
|
||||||
nil, all, method)
|
|
||||||
-- add to requests buffer
|
|
||||||
table.insert(requests, module_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- send requests
|
|
||||||
local pipeline_responses = http.pipeline_go(host, port, all)
|
|
||||||
if not pipeline_responses then
|
|
||||||
stdnse.debug1("No answers from pipelined requests", SCRIPT_NAME)
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
for i, response in pairs(pipeline_responses) do
|
|
||||||
-- Module exists if 200 on HEAD
|
|
||||||
-- or contains identification string for GET
|
|
||||||
if method == "HEAD" and response.status == 200 or
|
|
||||||
method == "GET" and response.status == 200 and
|
|
||||||
string.match(response.body, identification_string) then
|
|
||||||
table.insert(result, requests[i])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
Reference in New Issue
Block a user