Skip to content

Commit 8bec61e

Browse files
Add files via upload
1 parent d51c115 commit 8bec61e

File tree

1 file changed

+165
-61
lines changed

1 file changed

+165
-61
lines changed

http-wordpress-info.nse

+165-61
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ local string = require "string"
55

66
description = [[
77
Finds the WordPress version, theme and plugins observed in the page response.
8-
- Version detection tests for a meta generator html tag, if this is not found an attempt
9-
is made to access /readme.html a default file in all versions of WordPress.
8+
- WordPress version tests for a meta generator html tag, if this is not found an attempt
9+
is made to match version in page HTML or /feed/atom/ a default page in all versions of WordPress.
1010
- Theme is determined by searching HTML resposne for /wp-content/themes/$themename
1111
- Discovered plugins are those that match /wp-content/plugins/$pluginname in the HTML
1212
response. This will not find all plugins, to find all plugins you will need the
1313
http-wordpress-plugins nse script to brute force the plugin paths.
14+
- Additional checks are performed to match comments or other identifiers in the HTML for known plugins.
1415
15-
Script based on code from Michael Kohl's http-generator.nse
16+
Original script based on code from Michael Kohl's http-generator.nse
1617
]]
1718

1819
author = "Peter Hill <[email protected]>"
@@ -36,88 +37,191 @@ categories = {"default", "discovery", "safe"}
3637
-- @args http-wordpress-info.path Specify the path you want to check for a generator meta tag (default to '/').
3738
-- @args http-wordpress-info.redirects Specify the maximum number of redirects to follow (defaults to 3).
3839

40+
portrule = shortport.http
3941

40-
-- helper function
4142
local follow_redirects = function(host, port, path, n)
42-
local pattern = "^[hH][tT][tT][pP]/1.[01] 30[12]"
43-
local response = http.get(host, port, path)
44-
45-
while (response['status-line'] or ""):match(pattern) and n > 0 do
46-
n = n - 1
47-
local loc = response.header['location']
48-
response = http.get_url(loc)
49-
end
50-
51-
return response
43+
local loc
44+
local pattern = "^[hH][tT][tT][pP]/1.[01] 30[12]"
45+
local response = http.get(host, port, path)
46+
while (response['status-line'] or ""):match(pattern) and n > 0 do
47+
n = n - 1
48+
loc = response.header['location']
49+
response = http.get_url(loc)
50+
end
51+
return response, loc
5252
end
5353

54+
-- unique key for plugin slug
55+
-- can check multiple matches in value {}
56+
local plugin_patterns = {
57+
["autoptimize"] = {"autoptimize/js","autoptimize/css"},
58+
["w3-total-cache"] = {"by W3 Total Cache"},
59+
["wp-super-cache"] = {" generated by WP-Super-Cache"},
60+
["wordpress-seo"] = {"optimized with the Yoast SEO plugin"},
61+
["wordpress-seo-premium"] = {"optimized with the Yoast SEO Premium plugin"},
62+
["all-in-one-seo-pack"] = {"-- All in One SEO Pack"},
63+
["ubermenu"] = {"UberMenu CSS"},
64+
["hyper-cache"] = {"-- hyper cache 20"},
65+
["cookie-notice"] = {"Cookie Notice plugin"},
66+
["imp-download"] = {"Powered by iMP Download"},
67+
["optinmonster"] = {"-- This site is converting visitors into subscribers and customers with OptinMonster"},
68+
["duracelltomi-google-tag-manager"] = {"for WordPress by gtm4wp.com"},
69+
["woocommerce"] = {"generator[\"\'] content=[\"\']WooCommerce"},
70+
["custom-css-js"] = {"Simple Custom CSS and JS"},
71+
["simple-social-buttons"] = {"Tags generated by Simple Social Buttons"},
72+
["contact-form-7"] = {"var wpcf7 ="},
73+
["jetpack"] = {"-- Jetpack Open Graph Tags --","Jetpack Site Verification Tags --","jetpack.js"},
74+
["wp-rocket"] = {"-- This website is like a Rocket,"},
75+
["autodescription"] = {"The SEO Framework by Sybre Waaijer --"},
76+
["slider-revolution"] = {"--><p class=\"rs-p-wp","END REVOLUTION SLIDER"},
77+
["ninja-forms"] = {"ninja-forms-req-symbol"},
78+
["wp-fastest-cache"] = {"-- WP Fastest Cache file was created"},
79+
["easy-digital-downloads"] = {"var edd_scripts=", "content=\"Easy Digital Downloads"},
80+
["site-origin-panels"] = {"siteorigin-panels"},
81+
["litespeed-cache"] = {"litespeed-webfont-lib"},
82+
["elementor"] = {'class="elementor-'},
83+
["wordfence"] = {'WordfenceTestMonBot'},
84+
["divi-pagebuilder"] = {'.et_pb_column','.et_pb_section','.et_pb_row'},
85+
["addthis-share-buttons"] = {'-- AddThis Share Buttons'},
86+
["fusion-builder"] = {'fusion-builder-row'},
87+
["revslider"] = {'revslider'},
88+
["easy-digital-downloads"] = {"generator[\"\'] content=[\"\']Easy Digital Downloads","edd[\"\']:[\"\']Easy Digital Downloads"},
89+
["gravityforms"] = {"gravityForms", "Gravity Forms"},
90+
["LayerSlider"] = {"layerSlider"},
91+
["masterslider"] = {"MasterSlider"}
92+
}
5493

5594
-- find plugins in HTML page source and return table
56-
function parse_plugins_response (data)
95+
function parse_plugins_response (data, plugin_data)
5796
local result = {}
97+
local plugins_uniq = {}
5898
local pluginmatch = 'wp%-content/plugins/([0-9a-z%-.]+)'
59-
6099
for plugin in string.gmatch(data, pluginmatch) do
61-
if not stdnse.contains(result, plugin) then
62-
table.insert(result, plugin)
63-
end
100+
--table.insert(result, plugin)
101+
plugins_uniq[plugin] = plugin
102+
end
103+
for plugin, plugin_patterns in pairs(plugin_data) do
104+
for x, plugin_pattern in pairs(plugin_patterns) do
105+
if data:match(plugin_pattern) then
106+
plugins_uniq[plugin] = plugin
107+
-- table.insert(result, plugin)
108+
end
109+
end
110+
end
111+
for _, p in pairs(plugins_uniq) do
112+
table.insert(result, p)
64113
end
65114
return result
66115
end
67116

68117

69-
portrule = shortport.http
118+
function wp_version_check (response, host, port, path, loc)
119+
local wpver
120+
-- default patterns in page that match WordPress Core Version
121+
local patterns = {'<meta name=[\"\']generator[\"\'] content=[\"\']WordPress ([.0-9]+)[\"\'] ?/?>',
122+
'wp-includes/js/wp-embed.min.js?ver=([.0-9]+)',
123+
'wp-includes/js/comment-reply.min.js?ver=([.0-9]+)',
124+
'wp-emoji-release.min.js?ver=([.0-9]+)',
125+
'wp-includes/css/dist/block-library/style.min.css?ver=([.0-9]+)'}
126+
for key, pattern in pairs(patterns) do
127+
-- make pattern case-insensitive
128+
pattern = pattern:gsub("%a", function (c)
129+
return string.format("[%s%s]", string.lower(c),
130+
string.upper(c))
131+
end)
132+
if wpver == nil then
133+
wpver = response.body:match(pattern)
134+
end
135+
end
136+
-- Find version in /feed/atom/ default XML feed if no other match in html
137+
if ( not wpver and response.body:match("wp%-")) then
138+
local feedpattern = 'generator uri="https://wordpress.org/" version="([.0-9]*)'
139+
feedpath = path .. 'feed/atom/'
140+
-- if redirect occured get feed from loc
141+
if not loc then
142+
feedresponse = http.get(host, port, feedpath)
143+
else
144+
feedresponse = http.get_url(loc)
145+
end
146+
if ( feedresponse and feedresponse.body ) then
147+
wpver = feedresponse.body:match(feedpattern)
148+
end
149+
end
150+
return wpver
151+
end
152+
153+
-- php version from headers only
154+
-- http-php-version.nse makes additional requests
155+
function php_version_check (response)
156+
local phpmatch = 'PHP/([0-9.]+)'
157+
local phpver
158+
for name, value in pairs(response.header) do
159+
if value:match(phpmatch) then
160+
phpver = value:match(phpmatch)
161+
end
162+
end
163+
return(phpver)
164+
end
165+
166+
-- server header only (good if not using -sV to increase speed)
167+
function server_check (headers)
168+
local servermatch = '([a-zA-Z%-%/%)%(0-9.% ]+)'
169+
local server
170+
for name, value in pairs(headers) do
171+
if value:match(servermatch) and name == "server" then
172+
server = value:match(servermatch)
173+
end
174+
end
175+
return(server)
176+
end
177+
178+
70179

71180
action = function(host, port)
72-
local response, loc, generator
181+
local response, loc, generator, testcheck, plugins, themes, wpversion, phpversion, servercheck
73182
local path = stdnse.get_script_args('http-wordpress-info.path') or '/'
74-
local redirects = tonumber(stdnse.get_script_args('http-wordpress-info.redirects')) or 3
183+
local redirects = stdnse.get_script_args('http-wordpress-info.redirects') or 3
75184
local output_tab = stdnse.output_table()
76185

77186
-- Find Version in "meta generator tag"
78-
local pattern = '<meta name="?generator"? content="WordPress ([.0-9]*)" ?/?>'
79-
local themematch = 'wp%-content/themes/([0-9a-z]+)'
80-
81-
-- make pattern case-insensitive
82-
pattern = pattern:gsub("%a", function (c)
83-
return string.format("[%s%s]", string.lower(c),
84-
string.upper(c))
85-
end)
86-
87-
-- Find version in readme.html file
88-
local readmepattern = 'Version ([.0-9]*)'
89-
local wpversion = nil
90-
local themes = nil
91-
92-
93-
94-
response = follow_redirects(host, port, path, redirects)
187+
local themematch = 'wp%-content/themes/([0-9a-zA-Z%-]+)'
188+
189+
-- Get HTML to check for WordPress installation
190+
response, loc = follow_redirects( host, port, path, redirects )
191+
192+
stdnse.debug1("HTTP Status: %s", response["status-line"])
193+
output_tab.status = response["status"]
194+
output_tab.redirect = loc
95195
if ( response and response.body ) then
96-
wpversion = response.body:match(pattern)
196+
wpversion = wp_version_check(response, host, port, path, loc)
97197
themes = response.body:match(themematch)
98-
plugins = parse_plugins_response(response.body)
99-
end
100-
101-
-- If version not in generator tag, check /readme.html
102-
if ( not wpversion and response.body:match("wp%-content")) then
103-
readmepath = path .. '/readme.html'
104-
readmeresponse = follow_redirects(host, port, readmepath, redirects)
105-
if ( readmeresponse and readmeresponse.body ) then
106-
wpversion = readmeresponse.body:match(readmepattern)
198+
plugins = parse_plugins_response(response.body, plugin_patterns)
199+
phpversion = php_version_check(response)
200+
servercheck = server_check(response.header)
201+
202+
-- Store results in output table
203+
-- output_tab.testing = feedresponse.body
204+
if wpversion then
205+
output_tab.version = 'WordPress ' .. wpversion
206+
else
207+
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
208+
output_tab.version = 'WordPress ???'
209+
end
210+
end
211+
if ( themes and #themes > 0 ) then
212+
output_tab.theme = themes
213+
end
214+
if ( plugins and #plugins > 0 ) then
215+
output_tab.plugins = plugins
216+
end
217+
if ( phpversion ) then
218+
output_tab.php = phpversion
219+
end
220+
if ( servercheck ) then
221+
output_tab.server = servercheck
107222
end
108223
end
109-
110-
-- Store results in output table
111-
if wpversion then
112-
output_tab.version = 'WordPress ' .. wpversion
113-
end
114-
if ( themes and #themes > 0 ) then
115-
output_tab.theme = themes
116-
end
117-
if ( plugins and #plugins > 0 ) then
118-
output_tab.plugins = plugins
119-
end
120-
if ( output_tab.version or output_tab.plugins or output_tab.theme ) then
224+
if ( output_tab.version or output_tab.plugins or output_tab.theme or output_tab.status ) then
121225
return output_tab
122226
end
123227
end

0 commit comments

Comments
 (0)