Script Source
Toggle
The full script source is stored with this entry and is hidden by default to keep
the page easier to scan.
local http = require "http"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
description = [[
Finds the WordPress version, theme and plugins observed in the page response.
- WordPress version tests for a meta generator html tag, if this is not found an attempt
is made to match version in page HTML or /feed/atom/ a default page in all versions of WordPress.
- Theme is determined by searching HTML resposne for /wp-content/themes/$themename
- Discovered plugins are those that match /wp-content/plugins/$pluginname in the HTML
response. This will not find all plugins, to find all plugins you will need the
http-wordpress-plugins nse script to brute force the plugin paths.
- Additional checks are performed to match comments or other identifiers in the HTML for known plugins.
Original script based on code from Michael Kohl's http-generator.nse
]]
author = "Peter Hill <peter@hackertarget.com>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"default", "discovery", "safe"}
---
-- @usage
-- nmap --script http-wordpress-info [--script-args http-wordpress-info.path=<path>,http-wordpress-info.redirects=<number>,...] <host>
--
-- @output
-- PORT STATE SERVICE
-- 80/tcp open http
-- | http-wordpress-info:
-- | version: WordPress 4.0
-- | theme: canvas
-- | plugins:
-- | w3-total-cache
-- |_ simple-tooltips
-- @args http-wordpress-info.path Specify the path you want to check for a generator meta tag (default to '/').
-- @args http-wordpress-info.redirects Specify the maximum number of redirects to follow (defaults to 3).
portrule = shortport.http
local follow_redirects = function(host, port, path, n)
local loc
local pattern = "^[hH][tT][tT][pP]/1.[01] 30[12]"
local response = http.get(host, port, path)
while (response['status-line'] or ""):match(pattern) and n > 0 do
n = n - 1
loc = response.header['location']
response = http.get_url(loc)
end
return response, loc
end
-- unique key for plugin slug
-- can check multiple matches in value {}
local plugin_patterns = {
["autoptimize"] = {"autoptimize/js","autoptimize/css"},
["w3-total-cache"] = {"by W3 Total Cache"},
["wp-super-cache"] = {" generated by WP-Super-Cache"},
["wordpress-seo"] = {"optimized with the Yoast SEO plugin"},
["wordpress-seo-premium"] = {"optimized with the Yoast SEO Premium plugin"},
["all-in-one-seo-pack"] = {"-- All in One SEO Pack"},
["ubermenu"] = {"UberMenu CSS"},
["hyper-cache"] = {"-- hyper cache 20"},
["cookie-notice"] = {"Cookie Notice plugin"},
["imp-download"] = {"Powered by iMP Download"},
["optinmonster"] = {"-- This site is converting visitors into subscribers and customers with OptinMonster"},
["duracelltomi-google-tag-manager"] = {"for WordPress by gtm4wp.com"},
["woocommerce"] = {"generator[\"\'] content=[\"\']WooCommerce"},
["custom-css-js"] = {"Simple Custom CSS and JS"},
["simple-social-buttons"] = {"Tags generated by Simple Social Buttons"},
["contact-form-7"] = {"var wpcf7 ="},
["jetpack"] = {"-- Jetpack Open Graph Tags --","Jetpack Site Verification Tags --","jetpack.js"},
["wp-rocket"] = {"-- This website is like a Rocket,"},
["autodescription"] = {"The SEO Framework by Sybre Waaijer --"},
["slider-revolution"] = {"--><p class=\"rs-p-wp","END REVOLUTION SLIDER"},
["ninja-forms"] = {"ninja-forms-req-symbol"},
["wp-fastest-cache"] = {"-- WP Fastest Cache file was created"},
["easy-digital-downloads"] = {"var edd_scripts=", "content=\"Easy Digital Downloads"},
["site-origin-panels"] = {"siteorigin-panels"},
["litespeed-cache"] = {"litespeed-webfont-lib"},
["elementor"] = {'class="elementor-'},
["wordfence"] = {'WordfenceTestMonBot'},
["divi-pagebuilder"] = {'.et_pb_column','.et_pb_section','.et_pb_row'},
["addthis-share-buttons"] = {'-- AddThis Share Buttons'},
["fusion-builder"] = {'fusion-builder-row'},
["revslider"] = {'revslider'},
["easy-digital-downloads"] = {"generator[\"\'] content=[\"\']Easy Digital Downloads","edd[\"\']:[\"\']Easy Digital Downloads"},
["gravityforms"] = {"gravityForms", "Gravity Forms"},
["LayerSlider"] = {"layerSlider"},
["masterslider"] = {"MasterSlider"}
}
-- find plugins in HTML page source and return table
function parse_plugins_response (data, plugin_data)
local result = {}
local plugins_uniq = {}
local pluginmatch = 'wp%-content/plugins/([0-9a-z%-.]+)'
for plugin in string.gmatch(data, pluginmatch) do
--table.insert(result, plugin)
plugins_uniq[plugin] = plugin
end
for plugin, plugin_patterns in pairs(plugin_data) do
for x, plugin_pattern in pairs(plugin_patterns) do
if data:match(plugin_pattern) then
plugins_uniq[plugin] = plugin
-- table.insert(result, plugin)
end
end
end
for _, p in pairs(plugins_uniq) do
table.insert(result, p)
end
return result
end
function wp_version_check (response, host, port, path, loc)
local wpver
-- default patterns in page that match WordPress Core Version
local patterns = {'<meta name=[\"\']generator[\"\'] content=[\"\']WordPress ([.0-9]+)[\"\'] ?/?>',
'wp-includes/js/wp-embed.min.js?ver=([.0-9]+)',
'wp-includes/js/comment-reply.min.js?ver=([.0-9]+)',
'wp-emoji-release.min.js?ver=([.0-9]+)',
'wp-includes/css/dist/block-library/style.min.css?ver=([.0-9]+)'}
for key, pattern in pairs(patterns) do
-- make pattern case-insensitive
pattern = pattern:gsub("%a", function (c)
return string.format("[%s%s]", string.lower(c),
string.upper(c))
end)
if wpver == nil then
wpver = response.body:match(pattern)
end
end
-- Find version in /feed/atom/ default XML feed if no other match in html
if ( not wpver and response.body:match("wp%-")) then
local feedpattern = 'generator uri="https://wordpress.org/" version="([.0-9]*)'
feedpath = path .. 'feed/atom/'
-- if redirect occured get feed from loc
if not loc then
feedresponse = http.get(host, port, feedpath)
else
feedresponse = http.get_url(loc)
end
if ( feedresponse and feedresponse.body ) then
wpver = feedresponse.body:match(feedpattern)
end
end
return wpver
end
-- php version from headers only
-- http-php-version.nse makes additional requests
function php_version_check (response)
local phpmatch = 'PHP/([0-9.]+)'
local phpver
for name, value in pairs(response.header) do
if value:match(phpmatch) then
phpver = value:match(phpmatch)
end
end
return(phpver)
end
-- server header only (good if not using -sV to increase speed)
function server_check (headers)
local servermatch = '([a-zA-Z%-%/%)%(0-9.% ]+)'
local server
for name, value in pairs(headers) do
if value:match(servermatch) and name == "server" then
server = value:match(servermatch)
end
end
return(server)
end
action = function(host, port)
local response, loc, generator, testcheck, plugins, themes, wpversion, phpversion, servercheck
local path = stdnse.get_script_args('http-wordpress-info.path') or '/'
local redirects = stdnse.get_script_args('http-wordpress-info.redirects') or 3
local output_tab = stdnse.output_table()
-- Find Version in "meta generator tag"
local themematch = 'wp%-content/themes/([0-9a-zA-Z%-]+)'
-- Get HTML to check for WordPress installation
response, loc = follow_redirects( host, port, path, redirects )
stdnse.debug1("HTTP Status: %s", response["status-line"])
output_tab.status = response["status"]
output_tab.redirect = loc
if ( response and response.body ) then
wpversion = wp_version_check(response, host, port, path, loc)
themes = response.body:match(themematch)
plugins = parse_plugins_response(response.body, plugin_patterns)
phpversion = php_version_check(response)
servercheck = server_check(response.header)
-- Store results in output table
-- output_tab.testing = feedresponse.body
if wpversion then
output_tab.version = 'WordPress ' .. wpversion
else
if (response.body:match("wp%-json") or response.body:match("wp%-admin") or response.body:match("wp%-includes") or response.body:match("wp%-content")) and not wpversion then
output_tab.version = 'WordPress ???'
end
end
if ( themes and #themes > 0 ) then
output_tab.theme = themes
end
if ( plugins and #plugins > 0 ) then
output_tab.plugins = plugins
end
if ( phpversion ) then
output_tab.php = phpversion
end
if ( servercheck ) then
output_tab.server = servercheck
end
end
if ( output_tab.version or output_tab.plugins or output_tab.theme or output_tab.status ) then
return output_tab
end
end