@@ -5,14 +5,15 @@ local string = require "string"
5
5
6
6
description = [[
7
7
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.
10
10
- Theme is determined by searching HTML resposne for /wp-content/themes/$themename
11
11
- Discovered plugins are those that match /wp-content/plugins/$pluginname in the HTML
12
12
response. This will not find all plugins, to find all plugins you will need the
13
13
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.
14
15
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
16
17
]]
17
18
18
19
author = " Peter Hill <[email protected] >"
@@ -36,88 +37,191 @@ categories = {"default", "discovery", "safe"}
36
37
-- @args http-wordpress-info.path Specify the path you want to check for a generator meta tag (default to '/').
37
38
-- @args http-wordpress-info.redirects Specify the maximum number of redirects to follow (defaults to 3).
38
39
40
+ portrule = shortport .http
39
41
40
- -- helper function
41
42
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
52
52
end
53
53
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
+ }
54
93
55
94
-- find plugins in HTML page source and return table
56
- function parse_plugins_response (data )
95
+ function parse_plugins_response (data , plugin_data )
57
96
local result = {}
97
+ local plugins_uniq = {}
58
98
local pluginmatch = ' wp%-content/plugins/([0-9a-z%-.]+)'
59
-
60
99
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 )
64
113
end
65
114
return result
66
115
end
67
116
68
117
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
+
70
179
71
180
action = function (host , port )
72
- local response , loc , generator
181
+ local response , loc , generator , testcheck , plugins , themes , wpversion , phpversion , servercheck
73
182
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
75
184
local output_tab = stdnse .output_table ()
76
185
77
186
-- 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
95
195
if ( response and response .body ) then
96
- wpversion = response . body : match ( pattern )
196
+ wpversion = wp_version_check ( response , host , port , path , loc )
97
197
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
107
222
end
108
223
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
121
225
return output_tab
122
226
end
123
227
end
0 commit comments