mirror of
https://github.com/nmap/nmap.git
synced 2025-12-20 14:39:02 +00:00
Renames the original http-wordpress-enum to http-wordpress-users and adds the new version of http-wordpress-enum which detects plugins and themes of Wordpress installations
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added a new version of http-wordpress-enum, it now enumerates
|
||||||
|
plugins and themes of Wordpress installations. It also attempts to obtain
|
||||||
|
version information to detect outdated plugins. [Paulino Calderon]
|
||||||
|
|
||||||
|
o [NSE] Renamed http-wordpress-enum to http-wordpress-users in favor of
|
||||||
|
the new version of the script http-wordpress-enum which enumerates
|
||||||
|
plugins and themes of Wordpress installations. [Paulino Calderon]
|
||||||
|
|
||||||
o [NSE] Added a check for Cisco ASA version disclosure, CVE-2014-3398, to
|
o [NSE] Added a check for Cisco ASA version disclosure, CVE-2014-3398, to
|
||||||
http-enum in the 'security' category [Daniel Miller]
|
http-enum in the 'security' category [Daniel Miller]
|
||||||
|
|
||||||
|
|||||||
2639
nselib/data/wp-themes.lst
Normal file
2639
nselib/data/wp-themes.lst
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,146 +1,295 @@
|
|||||||
|
local coroutine = require "coroutine"
|
||||||
local http = require "http"
|
local http = require "http"
|
||||||
local io = require "io"
|
local io = require "io"
|
||||||
local nmap = require "nmap"
|
local nmap = require "nmap"
|
||||||
local shortport = require "shortport"
|
local shortport = require "shortport"
|
||||||
local stdnse = require "stdnse"
|
local stdnse = require "stdnse"
|
||||||
local string = require "string"
|
local string = require "string"
|
||||||
|
local table = require "table"
|
||||||
|
|
||||||
description = [[
|
description = [[
|
||||||
Enumerates usernames in Wordpress blog/CMS installations by exploiting an information disclosure vulnerability existing in versions 2.6, 3.1, 3.1.1, 3.1.3 and 3.2-beta2 and possibly others.
|
Enumerates themes and plugins of Wordpress installations. The script can also detect
|
||||||
|
outdated plugins by comparing version numbers with information pulled from api.wordpress.org.
|
||||||
|
|
||||||
Original advisory:
|
The script works with two separate databases for themes (wp-themes.lst) and plugins (wp-plugins.lst).
|
||||||
* http://www.talsoft.com.ar/index.php/research/security-advisories/wordpress-user-id-and-user-name-disclosure
|
The databases are sorted by popularity and the script will search only the top 100 entries by default.
|
||||||
|
The theme database has around 32,000 entries while the plugin database has around 14,000 entries.
|
||||||
|
|
||||||
|
The script determines the version number of a plugin by looking at the readme.txt file inside the plugin
|
||||||
|
directory and it uses the file style.css inside a theme directory to determine the theme version.
|
||||||
|
If the script argument check-latest is set to true, the script will query api.wordpress.org to obtain
|
||||||
|
the latest version number available. This check is disabled by default since it queries an external service.
|
||||||
|
|
||||||
|
This script is a combination of http-wordpress-plugins.nse and http-wordpress-themes.nse originally
|
||||||
|
submited by Ange Gutek and Peter Hill.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
-Implement version checking for themes.
|
||||||
]]
|
]]
|
||||||
|
|
||||||
---
|
---
|
||||||
-- @usage
|
-- @usage nmap -sV --script http-wordpress-enum <target>
|
||||||
-- nmap -p80 --script http-wordpress-enum <target>
|
-- @usage nmap --script http-wordpress-enum --script-args check-latest=true,search-limit=10 <target>
|
||||||
-- nmap -sV --script http-wordpress-enum --script-args limit=50 <target>
|
-- @usage nmap --script http-wordpress-enum --script-args type="themes" <target>
|
||||||
|
--
|
||||||
|
-- @args http-wordpress-enum.root Base path. By default the script will try to find a WP directory
|
||||||
|
-- installation or fall back to '/'.
|
||||||
|
-- @args http-wordpress-enum.search-limit Number of entries or the string "all". Default:100.
|
||||||
|
-- @args http-wordpress-enum.type Search type. Available options:plugins, themes or all. Default:all.
|
||||||
|
-- @args http-wordpress-enum.check-latest Enables version check. Default:false.
|
||||||
--
|
--
|
||||||
-- @output
|
-- @output
|
||||||
-- PORT STATE SERVICE REASON
|
-- PORT STATE SERVICE
|
||||||
-- 80/tcp open http syn-ack
|
-- 80/tcp open http
|
||||||
-- | http-wordpress-enum:
|
-- | http-wordpress-enum:
|
||||||
-- | Username found: admin
|
-- | Search limited to top 100 themes/plugins
|
||||||
-- | Username found: mauricio
|
-- | plugins
|
||||||
-- | Username found: cesar
|
-- | akismet
|
||||||
-- | Username found: lean
|
-- | contact-form-7 4.1 (latest version:4.1)
|
||||||
-- | Username found: alex
|
-- | all-in-one-seo-pack (latest version:2.2.5.1)
|
||||||
-- | Username found: ricardo
|
-- | google-sitemap-generator 4.0.7.1 (latest version:4.0.8)
|
||||||
-- |_Search stopped at ID #25. Increase the upper limit if necessary with 'http-wordpress-enum.limit'
|
-- | jetpack 3.3 (latest version:3.3)
|
||||||
|
-- | wordfence 5.3.6 (latest version:5.3.6)
|
||||||
|
-- | better-wp-security 4.6.4 (latest version:4.6.6)
|
||||||
|
-- | google-analytics-for-wordpress 5.3 (latest version:5.3)
|
||||||
|
-- | themes
|
||||||
|
-- | twentytwelve
|
||||||
|
-- |_ twentyfourteen
|
||||||
--
|
--
|
||||||
-- @args http-wordpress-enum.limit Upper limit for ID search. Default: 25
|
-- @xmloutput
|
||||||
-- @args http-wordpress-enum.basepath Base path to Wordpress. Default: /
|
-- <table key="google-analytics-for-wordpress">
|
||||||
-- @args http-wordpress-enum.out If set it saves the username list in this file.
|
-- <elem key="installation_version">5.1</elem>
|
||||||
|
-- <elem key="latest_version">5.3</elem>
|
||||||
|
-- <elem key="name">google-analytics-for-wordpress</elem>
|
||||||
|
-- <elem key="path">/wp-content/plugins/google-analytics-for-wordpress/</elem>
|
||||||
|
-- <elem key="category">plugins</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <table key="twentytwelve">
|
||||||
|
-- <elem key="category">themes</elem>
|
||||||
|
-- <elem key="path">/wp-content/themes/twentytwelve/</elem>
|
||||||
|
-- <elem key="name">twentytwelve</elem>
|
||||||
|
-- </table>
|
||||||
|
-- <elem key="title">Search limited to top 100 themes/plugins</elem>
|
||||||
---
|
---
|
||||||
|
|
||||||
author = "Paulino Calderon <calderon@websec.mx>"
|
author = {"Ange Gutek", "Peter Hill", "Gyanendra Mishra", "Paulino Calderon"}
|
||||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
||||||
categories = {"auth", "intrusive", "vuln"}
|
|
||||||
|
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
|
||||||
|
categories = {"discovery", "intrusive"}
|
||||||
|
|
||||||
|
local DEFAULT_SEARCH_LIMIT = 100
|
||||||
|
local DEFAULT_PLUGINS_PATH = '/wp-content/plugins/'
|
||||||
|
local WORDPRESS_API_URL = 'http://api.wordpress.org/plugins/info/1.0/'
|
||||||
|
|
||||||
portrule = shortport.http
|
portrule = shortport.http
|
||||||
|
|
||||||
---
|
--Reads database
|
||||||
-- Returns the username extracted from the url corresponding to the id passed
|
local function read_data_file(file)
|
||||||
-- If user id doesn't exists returns false
|
return coroutine.wrap(function()
|
||||||
-- @param host Host table
|
for line in file:lines() do
|
||||||
-- @param port Port table
|
if not line:match("^%s*#") and not line:match("^%s*$") then
|
||||||
-- @param path Base path to WP
|
coroutine.yield(line)
|
||||||
-- @param id User id
|
|
||||||
-- @return false if not found otherwise it returns the username
|
|
||||||
---
|
|
||||||
local function get_wp_user(host, port, path, id)
|
|
||||||
stdnse.debug2("Trying to get username with id %s", id)
|
|
||||||
local req = http.get(host, port, path.."?author="..id, { no_cache = true})
|
|
||||||
if req.status then
|
|
||||||
stdnse.debug1("User id #%s returned status %s", id, req.status)
|
|
||||||
if req.status == 301 then
|
|
||||||
local _, _, user = string.find(req.header.location, 'https?://.*/.*/(.*)/')
|
|
||||||
return user
|
|
||||||
elseif req.status == 200 then
|
|
||||||
-- Users with no posts get a 200 response, but the name is in an RSS link.
|
|
||||||
-- http://seclists.org/nmap-dev/2011/q3/812
|
|
||||||
local _, _, user = string.find(req.body, 'https?://.-/author/(.-)/feed/')
|
|
||||||
return user
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--Checks if the plugin/theme file exists
|
||||||
|
local function existence_check_assign(act_file)
|
||||||
|
if not act_file then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
local temp_file = io.open(act_file,"r")
|
||||||
---
|
if not temp_file then
|
||||||
--Returns true if WP installation exists.
|
|
||||||
--We assume an installation exists if wp-login.php is found
|
|
||||||
--@param host Host table
|
|
||||||
--@param port Port table
|
|
||||||
--@param path Path to WP
|
|
||||||
--@return True if WP was found
|
|
||||||
--
|
|
||||||
local function check_wp(host, port, path)
|
|
||||||
stdnse.debug2("Checking %swp-login.php ", path)
|
|
||||||
local req = http.get(host, port, path.."wp-login.php", {no_cache=true})
|
|
||||||
if req.status and req.status == 200 then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
return temp_file
|
||||||
---
|
|
||||||
--Writes string to file
|
|
||||||
--Taken from: hostmap.nse
|
|
||||||
--@param filename Target filename
|
|
||||||
--@param contents String to save
|
|
||||||
--@return true when successful
|
|
||||||
local function write_file(filename, contents)
|
|
||||||
local f, err = io.open(filename, "w")
|
|
||||||
if not f then
|
|
||||||
return f, err
|
|
||||||
end
|
|
||||||
f:write(contents)
|
|
||||||
f:close()
|
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--Obtains version from readme.txt or style.css
|
||||||
|
local function get_version(path, typeof, host, port)
|
||||||
|
local pattern, version, versioncheck
|
||||||
|
|
||||||
|
if typeof == 'plugins' then
|
||||||
|
path = path .. "readme.txt"
|
||||||
|
pattern = 'Stable tag: ([.0-9]*)'
|
||||||
|
else
|
||||||
|
path = path .. "style.css"
|
||||||
|
pattern = 'Version: ([.0-9]*)'
|
||||||
|
end
|
||||||
|
|
||||||
|
stdnse.debug1("Extracting version of path:%s", path)
|
||||||
|
versioncheck = http.get(host, port, path)
|
||||||
|
if versioncheck.body then
|
||||||
|
version = versioncheck.body:match(pattern)
|
||||||
|
end
|
||||||
|
stdnse.debug1("Version found:", version)
|
||||||
|
return version
|
||||||
|
end
|
||||||
|
|
||||||
|
-- check if the plugin is the latest
|
||||||
|
local function get_latest_plugin_version(plugin)
|
||||||
|
stdnse.debug1("Retrieving the latest version of %s", plugin)
|
||||||
|
local apiurl = WORDPRESS_API_URL .. plugin .. ".json"
|
||||||
|
local latestpluginapi = http.get('api.wordpress.org', '80', apiurl)
|
||||||
|
local latestpluginpattern = '","version":"([.0-9]*)'
|
||||||
|
local latestpluginversion = latestpluginapi.body:match(latestpluginpattern)
|
||||||
|
stdnse.debug1("Latest version:%s", latestpluginversion)
|
||||||
|
return latestpluginversion
|
||||||
|
end
|
||||||
|
|
||||||
---
|
|
||||||
--MAIN
|
|
||||||
---
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
local basepath = stdnse.get_script_args("http-wordpress-enum.basepath") or "/"
|
|
||||||
local limit = stdnse.get_script_args("http-wordpress-enum.limit") or 25
|
local result = {}
|
||||||
local filewrite = stdnse.get_script_args("http-wordpress-enum.out")
|
local file = {}
|
||||||
local output = {""}
|
local all = {}
|
||||||
local users = {}
|
local bfqueries = {}
|
||||||
--First, we check this is WP
|
local wp_autoroot
|
||||||
if not(check_wp(host, port, basepath)) then
|
local output_table = stdnse.output_table()
|
||||||
if nmap.verbosity() >= 2 then
|
|
||||||
return "[Error] Wordpress installation was not found. We couldn't find wp-login.php"
|
--Read script arguments
|
||||||
|
local operation_type_arg = stdnse.get_script_args(SCRIPT_NAME .. ".type") or "all"
|
||||||
|
local apicheck = stdnse.get_script_args(SCRIPT_NAME .. ".check-latest")
|
||||||
|
local wp_root = stdnse.get_script_args(SCRIPT_NAME .. ".root")
|
||||||
|
local resource_search_arg = stdnse.get_script_args(SCRIPT_NAME .. ".search-limit") or DEFAULT_SEARCH_LIMIT
|
||||||
|
|
||||||
|
local wp_themes_file = nmap.fetchfile("nselib/data/wp-themes.lst")
|
||||||
|
local wp_plugins_file = nmap.fetchfile("nselib/data/wp-plugins.lst")
|
||||||
|
|
||||||
|
if operation_type_arg == "themes" or operation_type_arg == "all" then
|
||||||
|
local theme_db = existence_check_assign(wp_themes_file)
|
||||||
|
if not theme_db then
|
||||||
|
return false, "Couldn't find wp-themes.lst in /nselib/data/"
|
||||||
else
|
else
|
||||||
return
|
file['themes'] = theme_db
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if operation_type_arg == "plugins" or operation_type_arg == "all" then
|
||||||
--Incrementing ids to enum users
|
local plugin_db = existence_check_assign(wp_plugins_file)
|
||||||
for i=1, tonumber(limit) do
|
if not plugin_db then
|
||||||
local user = get_wp_user(host, port, basepath, i)
|
return false, "Couldn't find wp-plugins.lst in /nselib/data/"
|
||||||
if user then
|
|
||||||
stdnse.debug1("Username found -> %s", user)
|
|
||||||
output[#output+1] = string.format("Username found: %s", user)
|
|
||||||
users[#users+1] = user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if filewrite and #users>0 then
|
|
||||||
local status, err = write_file(filewrite, stdnse.strjoin("\n", users))
|
|
||||||
if status then
|
|
||||||
output[#output+1] = string.format("Users saved to %s\n", filewrite)
|
|
||||||
else
|
else
|
||||||
output[#output+1] = string.format("Error saving %s: %s\n", filewrite, err)
|
file['plugins'] = plugin_db
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if #output > 1 then
|
if resource_search_arg == "all" then
|
||||||
output[#output+1] = string.format("Search stopped at ID #%s. Increase the upper limit if necessary with 'http-wordpress-enum.limit'", limit)
|
resource_search = nil
|
||||||
return stdnse.strjoin("\n", output)
|
else
|
||||||
|
resource_search = tonumber(resource_search_arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- search the website root for evidences of a Wordpress path
|
||||||
|
if not wp_root then
|
||||||
|
local target_index = http.get(host,port, "/")
|
||||||
|
|
||||||
|
if target_index.status and target_index.body then
|
||||||
|
wp_autoroot = string.match(target_index.body, "http://[%w%-%.]-/([%w%-%./]-)wp%-content")
|
||||||
|
if wp_autoroot then
|
||||||
|
wp_autoroot = "/" .. wp_autoroot
|
||||||
|
stdnse.debug(1,"WP root directory: %s", wp_autoroot)
|
||||||
|
else
|
||||||
|
stdnse.debug(1,"WP root directory: wp_autoroot was unable to find a WP content dir (root page returns %d).", target_index.status)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--identify the 404, the script cant handle ambiguous responses
|
||||||
|
local status_404, result_404, body_404 = http.identify_404(host, port)
|
||||||
|
if not status_404 then
|
||||||
|
return stdnse.format_output(false, SCRIPT_NAME .. " unable to handle 404 pages (" .. result_404 .. ")")
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
--build a table of both directories to brute force and the corresponding WP resources' name
|
||||||
|
local resource_count=0
|
||||||
|
for key,value in pairs(file) do
|
||||||
|
local l_file = value
|
||||||
|
resource_count = 0
|
||||||
|
for line in read_data_file(l_file) do
|
||||||
|
if resource_search and resource_count >= resource_search then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
|
||||||
|
local target
|
||||||
|
if wp_root then
|
||||||
|
-- Give user-supplied argument the priority
|
||||||
|
target = wp_root .. string.gsub(DEFAULT_PLUGINS_PATH, "plugins", key) .. line .. "/"
|
||||||
|
elseif wp_autoroot then
|
||||||
|
-- Maybe the script has discovered another Wordpress content directory
|
||||||
|
target = wp_autoroot .. string.gsub(DEFAULT_PLUGINS_PATH, "plugins", key) .. line .. "/"
|
||||||
|
else
|
||||||
|
-- Default WP directory is root
|
||||||
|
target = string.gsub(DEFAULT_PLUGINS_PATH, "plugins", key) .. line .. "/"
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
target = string.gsub(target, "//", "/")
|
||||||
|
table.insert(bfqueries, {target, line})
|
||||||
|
all = http.pipeline_add(target, nil, all, "GET")
|
||||||
|
resource_count = resource_count + 1
|
||||||
|
|
||||||
|
end
|
||||||
|
-- release hell...
|
||||||
|
local pipeline_returns = http.pipeline_go(host, port, all)
|
||||||
|
if not pipeline_returns then
|
||||||
|
stdnse.print_verbose(1,"got no answers from pipelined queries")
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local response = {}
|
||||||
|
response['name'] = key
|
||||||
|
for i, data in pairs(pipeline_returns) do
|
||||||
|
-- if it's not a four-'o-four, it probably means that the plugin is present
|
||||||
|
if http.page_exists(data, result_404, body_404, bfqueries[i][1], true) then
|
||||||
|
stdnse.debug(1,"Found a plugin/theme:%s", bfqueries[i][2])
|
||||||
|
local version = get_version(bfqueries[i][1],key,host,port)
|
||||||
|
local output = nil
|
||||||
|
|
||||||
|
--We format the table for XML output
|
||||||
|
bfqueries[i].path = bfqueries[i][1]
|
||||||
|
bfqueries[i].category = key
|
||||||
|
bfqueries[i].name = bfqueries[i][2]
|
||||||
|
bfqueries[i][1] = nil
|
||||||
|
bfqueries[i][2] = nil
|
||||||
|
|
||||||
|
if version then
|
||||||
|
output = bfqueries[i].name .." ".. version
|
||||||
|
bfqueries[i].installation_version = version
|
||||||
|
--Right now we can only get the version number of plugins through api.wordpress.org
|
||||||
|
if apicheck == "true" and key=="plugins" then
|
||||||
|
latestversion = get_latest_plugin_version(bfqueries[i].name)
|
||||||
|
if latestversion then
|
||||||
|
output = output .. " (latest version:" .. latestversion .. ")"
|
||||||
|
bfqueries[i].latest_version = latestversion
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
output = bfqueries[i].name
|
||||||
|
end
|
||||||
|
output_table[bfqueries[i].name] = bfqueries[i]
|
||||||
|
table.insert(response, output)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(result, response)
|
||||||
|
bfqueries={}
|
||||||
|
all = {}
|
||||||
|
|
||||||
|
end
|
||||||
|
local len = 0
|
||||||
|
for i, v in ipairs(result) do len = len >= #v and len or #v end
|
||||||
|
if len > 0 then
|
||||||
|
output_table.title = string.format("Search limited to top %s themes/plugins", resource_count)
|
||||||
|
result.name = output_table.title
|
||||||
|
return output_table, stdnse.format_output(true, result)
|
||||||
|
else
|
||||||
|
if nmap.verbosity()>1 then
|
||||||
|
return string.format("Nothing found amongst the top %s resources,"..
|
||||||
|
"use --script-args search-limit=<number|all> for deeper analysis)", resource_count)
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|||||||
146
scripts/http-wordpress-users.nse
Normal file
146
scripts/http-wordpress-users.nse
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
local http = require "http"
|
||||||
|
local io = require "io"
|
||||||
|
local nmap = require "nmap"
|
||||||
|
local shortport = require "shortport"
|
||||||
|
local stdnse = require "stdnse"
|
||||||
|
local string = require "string"
|
||||||
|
|
||||||
|
description = [[
|
||||||
|
Enumerates usernames in Wordpress blog/CMS installations by exploiting an information disclosure vulnerability existing in versions 2.6, 3.1, 3.1.1, 3.1.3 and 3.2-beta2 and possibly others.
|
||||||
|
|
||||||
|
Original advisory:
|
||||||
|
* http://www.talsoft.com.ar/index.php/research/security-advisories/wordpress-user-id-and-user-name-disclosure
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p80 --script http-wordpress-users <target>
|
||||||
|
-- nmap -sV --script http-wordpress-users --script-args limit=50 <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-wordpress-users:
|
||||||
|
-- | Username found: admin
|
||||||
|
-- | Username found: mauricio
|
||||||
|
-- | Username found: cesar
|
||||||
|
-- | Username found: lean
|
||||||
|
-- | Username found: alex
|
||||||
|
-- | Username found: ricardo
|
||||||
|
-- |_Search stopped at ID #25. Increase the upper limit if necessary with 'http-wordpress-users.limit'
|
||||||
|
--
|
||||||
|
-- @args http-wordpress-users.limit Upper limit for ID search. Default: 25
|
||||||
|
-- @args http-wordpress-users.basepath Base path to Wordpress. Default: /
|
||||||
|
-- @args http-wordpress-users.out If set it saves the username list in this file.
|
||||||
|
---
|
||||||
|
|
||||||
|
author = "Paulino Calderon <calderon@websec.mx>"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"auth", "intrusive", "vuln"}
|
||||||
|
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns the username extracted from the url corresponding to the id passed
|
||||||
|
-- If user id doesn't exists returns false
|
||||||
|
-- @param host Host table
|
||||||
|
-- @param port Port table
|
||||||
|
-- @param path Base path to WP
|
||||||
|
-- @param id User id
|
||||||
|
-- @return false if not found otherwise it returns the username
|
||||||
|
---
|
||||||
|
local function get_wp_user(host, port, path, id)
|
||||||
|
stdnse.debug2("Trying to get username with id %s", id)
|
||||||
|
local req = http.get(host, port, path.."?author="..id, { no_cache = true})
|
||||||
|
if req.status then
|
||||||
|
stdnse.debug1("User id #%s returned status %s", id, req.status)
|
||||||
|
if req.status == 301 then
|
||||||
|
local _, _, user = string.find(req.header.location, 'https?://.*/.*/(.*)/')
|
||||||
|
return user
|
||||||
|
elseif req.status == 200 then
|
||||||
|
-- Users with no posts get a 200 response, but the name is in an RSS link.
|
||||||
|
-- http://seclists.org/nmap-dev/2011/q3/812
|
||||||
|
local _, _, user = string.find(req.body, 'https?://.-/author/(.-)/feed/')
|
||||||
|
return user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
--Returns true if WP installation exists.
|
||||||
|
--We assume an installation exists if wp-login.php is found
|
||||||
|
--@param host Host table
|
||||||
|
--@param port Port table
|
||||||
|
--@param path Path to WP
|
||||||
|
--@return True if WP was found
|
||||||
|
--
|
||||||
|
local function check_wp(host, port, path)
|
||||||
|
stdnse.debug2("Checking %swp-login.php ", path)
|
||||||
|
local req = http.get(host, port, path.."wp-login.php", {no_cache=true})
|
||||||
|
if req.status and req.status == 200 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
--Writes string to file
|
||||||
|
--Taken from: hostmap.nse
|
||||||
|
--@param filename Target filename
|
||||||
|
--@param contents String to save
|
||||||
|
--@return true when successful
|
||||||
|
local function write_file(filename, contents)
|
||||||
|
local f, err = io.open(filename, "w")
|
||||||
|
if not f then
|
||||||
|
return f, err
|
||||||
|
end
|
||||||
|
f:write(contents)
|
||||||
|
f:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
--MAIN
|
||||||
|
---
|
||||||
|
action = function(host, port)
|
||||||
|
local basepath = stdnse.get_script_args(SCRIPT_NAME .. ".basepath") or "/"
|
||||||
|
local limit = stdnse.get_script_args(SCRIPT_NAME .. ".limit") or 25
|
||||||
|
local filewrite = stdnse.get_script_args(SCRIPT_NAME .. ".out")
|
||||||
|
local output = {""}
|
||||||
|
local users = {}
|
||||||
|
--First, we check this is WP
|
||||||
|
if not(check_wp(host, port, basepath)) then
|
||||||
|
if nmap.verbosity() >= 2 then
|
||||||
|
return "[Error] Wordpress installation was not found. We couldn't find wp-login.php"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--Incrementing ids to enum users
|
||||||
|
for i=1, tonumber(limit) do
|
||||||
|
local user = get_wp_user(host, port, basepath, i)
|
||||||
|
if user then
|
||||||
|
stdnse.debug1("Username found -> %s", user)
|
||||||
|
output[#output+1] = string.format("Username found: %s", user)
|
||||||
|
users[#users+1] = user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if filewrite and #users>0 then
|
||||||
|
local status, err = write_file(filewrite, stdnse.strjoin("\n", users))
|
||||||
|
if status then
|
||||||
|
output[#output+1] = string.format("Users saved to %s\n", filewrite)
|
||||||
|
else
|
||||||
|
output[#output+1] = string.format("Error saving %s: %s\n", filewrite, err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #output > 1 then
|
||||||
|
output[#output+1] = string.format("Search stopped at ID #%s. Increase the upper limit if necessary with 'http-wordpress-users.limit'", limit)
|
||||||
|
return stdnse.strjoin("\n", output)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -239,8 +239,8 @@ Entry { filename = "http-vuln-wnr1000-creds.nse", categories = { "exploit", "int
|
|||||||
Entry { filename = "http-waf-detect.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-waf-detect.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "http-waf-fingerprint.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-waf-fingerprint.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "http-wordpress-brute.nse", categories = { "brute", "intrusive", } }
|
Entry { filename = "http-wordpress-brute.nse", categories = { "brute", "intrusive", } }
|
||||||
Entry { filename = "http-wordpress-enum.nse", categories = { "auth", "intrusive", "vuln", } }
|
Entry { filename = "http-wordpress-enum.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "http-wordpress-plugins.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-wordpress-users.nse", categories = { "auth", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-xssed.nse", categories = { "discovery", "external", "safe", } }
|
Entry { filename = "http-xssed.nse", categories = { "discovery", "external", "safe", } }
|
||||||
Entry { filename = "iax2-brute.nse", categories = { "brute", "intrusive", } }
|
Entry { filename = "iax2-brute.nse", categories = { "brute", "intrusive", } }
|
||||||
Entry { filename = "iax2-version.nse", categories = { "version", } }
|
Entry { filename = "iax2-version.nse", categories = { "version", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user