1
0
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:
gyani
2015-08-13 11:58:44 +00:00
parent 69345854ee
commit ba873c28c0
4 changed files with 19001 additions and 9884 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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