-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathhttp-wordpress-themes.nse
192 lines (152 loc) · 6.77 KB
/
http-wordpress-themes.nse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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 = [[
Discover a list of installed WordPress themes. Brute force of the theme path
/wp-content/themes/$themename/ testing for known themes. As seen in the widespread
timthumb vulnerability themes installed but not activated can still be exploited.
After detection of a valid theme path, the script will attempt to GET the style.css
of the theme. This is a standard file in WordPress themes that contains the theme
version, if found the version will be included in the output.
The theme list has been created and sorted by theme popularity after crawling the
top 1 million sites. Also includes themes from wordpress.org. Anything but a 404
means that a given theme directory probably exists, so the theme probably also does.
The available themes for Wordpress is huge and despite the efforts of Nmap to
parallelize the queries, a whole search could take an hour or so. That's why
the theme list is sorted by popularity and by default the script will only
check the first 100 ones. Users can tweak this with an option (see below).
NSE Script is a clone of the http-wordpress-plugins.nse script by Ange Gutek.
]]
---
-- @args http-wordpress-themes.root If set, points to the blog root directory on the website. If not, the script will try to find a WP directory installation or fall back to root.
-- @args http-wordpress-themes.search As the themes list contains about 3000 themes, this script will only search the 100 most popular ones by default.
-- Use this option with a number or "all" as an argument for a more comprehensive brute force.
--
-- @usage
-- nmap --script=http-wordpress-themes --script-args http-wordpress-themes.root="/blog/",http-wordpress-themes.search=500 <targets>
--
--@output
-- Interesting ports on my.woot.blog (123.123.123.123):
-- PORT STATE SERVICE REASON
-- 80/tcp open http syn-ack
-- | http-wordpress-themes:
-- | search amongst the 500 most popular themes
-- | twentyfourteen 1.3
-- | canvas 5.8.7
-- |_ twentytwelve 1.5
--
-- Created 18/10/2014 - v0.1 - created (and themes crawled) by Peter Hill <peter@hackertarget.com>
author = "Peter Hill <peter@hackertarget.com>"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "intrusive"}
local DEFAULT_THEME_SEARCH = 100
portrule = shortport.service("http")
local function read_data_file(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
action = function(host, port)
local result = {}
local all = {}
local bfqueries = {}
--Check if the wp themes list exists
local wp_themes_file = nmap.fetchfile("nselib/data/wp-themes.lst")
if not wp_themes_file then
return false, "Couldn't find wp-themes.lst (should be in nselib/data)"
end
local file = io.open(wp_themes_file, "r")
if not file then
return false, "Couldn't find wp-themes.lst (should be in nselib/data)"
end
local wp_autoroot
local wp_root = stdnse.get_script_args("http-wordpress-themes.root")
local themes_search = DEFAULT_THEME_SEARCH
local themes_search_arg = stdnse.get_script_args("http-wordpress-themes.search")
if themes_search_arg == "all" then
themes_search = nil
elseif themes_search_arg then
themes_search = tonumber(themes_search_arg)
end
stdnse.print_debug(1, "%s themes search range: %s", SCRIPT_NAME, themes_search or "unlimited")
-- 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.print_debug(1, "%s WP root directory: %s", SCRIPT_NAME, wp_autoroot)
else
stdnse.print_debug(1, "%s WP root directory: wp_autoroot was unable to find a WP content dir (root page returns %d).", SCRIPT_NAME, target_index.status)
end
end
end
--identify the 404
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 themes' name
local theme_count = 0
for line in read_data_file(file) do
if themes_search and theme_count >= themes_search then
break
end
local target
if wp_root then
-- Give user-supplied argument the priority
target = wp_root .. "/wp-content/themes/" .. line .. "/"
elseif wp_autoroot then
-- Maybe the script has discovered another Wordpress content directory
target = wp_autoroot .. "wp-content/themes/" .. line .. "/"
else
-- Default WP directory is root
target = "/wp-content/themes/" .. line .. "/"
end
target = string.gsub(target, "//", "/")
table.insert(bfqueries, {target, line})
all = http.pipeline_add(target, nil, all, "GET")
theme_count = theme_count + 1
end
-- release hell...
local pipeline_returns = http.pipeline_go(host, port, all)
if not pipeline_returns then
stdnse.print_debug(1, "%s : got no answers from pipelined queries", SCRIPT_NAME)
end
for i, data in pairs(pipeline_returns) do
-- if it's not a four-'o-four, it probably means that the theme is present
if http.page_exists(data, result_404, body_404, bfqueries[i][1], true) then
stdnse.print_debug(1, "http-wordpress-themes.nse: Found a theme: %s", bfqueries[i][2])
-- now try and get the version of the plugin from readme.txt
stylecsspath = bfqueries[i][1] .. "style.css"
local themeversion = nil
themefound = nil
stdnse.print_debug(1, "http-wordpress-plugins.nse: Style CSS path: %s", stylecsspath)
local themeversioncheck = http.get(host, port, stylecsspath)
local versionpattern = 'Version: ([.0-9]*)'
local themeversion = themeversioncheck.body:match(versionpattern)
if themeversion then
themefound = bfqueries[i][2] .. " " .. themeversion
else
themefound = bfqueries[i][2]
end
table.insert(result, themefound)
end
end
if #result > 0 then
result.name = "search amongst the " .. theme_count .. " most popular themes"
return stdnse.format_output(true, result)
else
return "nothing found amongst the " .. theme_count .. " most popular themes, use --script-args http-wordpress-themes.search=<number|all> for deeper analysis)\n"
end
end