|
| 1 | +local http = require "http" |
| 2 | +local shortport = require "shortport" |
| 3 | +local stdnse = require "stdnse" |
| 4 | +local string = require "string" |
| 5 | + |
| 6 | +description = [[ |
| 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. |
| 10 | +- Theme is determined by searching HTML resposne for /wp-content/themes/$themename |
| 11 | +- Discovered plugins are those that match /wp-content/plugins/$pluginname in the HTML |
| 12 | +response. This will not find all plugins, to find all plugins you will need the |
| 13 | +http-wordpress-plugins nse script to brute force the plugin paths. |
| 14 | +
|
| 15 | +Script based on code from Michael Kohl's http-generator.nse |
| 16 | +]] |
| 17 | + |
| 18 | +author = "Peter Hill <[email protected]>" |
| 19 | +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" |
| 20 | +categories = {"default", "discovery", "safe"} |
| 21 | + |
| 22 | +--- |
| 23 | +-- @usage |
| 24 | +-- nmap --script http-wordpress-info [--script-args http-wordpress-info.path=<path>,http-wordpress-info.redirects=<number>,...] <host> |
| 25 | +-- |
| 26 | +-- @output |
| 27 | +-- PORT STATE SERVICE |
| 28 | +-- 80/tcp open http |
| 29 | +-- | http-wordpress-info: |
| 30 | +-- | version: WordPress 4.0 |
| 31 | +-- | theme: canvas |
| 32 | +-- | plugins: |
| 33 | +-- | w3-total-cache |
| 34 | +-- |_ simple-tooltips |
| 35 | + |
| 36 | +-- @args http-wordpress-info.path Specify the path you want to check for a generator meta tag (default to '/'). |
| 37 | +-- @args http-wordpress-info.redirects Specify the maximum number of redirects to follow (defaults to 3). |
| 38 | + |
| 39 | + |
| 40 | +-- helper function |
| 41 | +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 |
| 52 | +end |
| 53 | + |
| 54 | + |
| 55 | +-- find plugins in HTML page source and return table |
| 56 | +function parse_plugins_response (data) |
| 57 | + local result = {} |
| 58 | + local pluginmatch = 'wp%-content/plugins/([0-9a-z%-.]+)' |
| 59 | + |
| 60 | + for plugin in string.gmatch(data, pluginmatch) do |
| 61 | + if not stdnse.contains(result, plugin) then |
| 62 | + table.insert(result, plugin) |
| 63 | + end |
| 64 | + end |
| 65 | + return result |
| 66 | +end |
| 67 | + |
| 68 | + |
| 69 | +portrule = shortport.http |
| 70 | + |
| 71 | +action = function(host, port) |
| 72 | + local response, loc, generator |
| 73 | + 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 |
| 75 | + local output_tab = stdnse.output_table() |
| 76 | + |
| 77 | + -- 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) |
| 95 | + if ( response and response.body ) then |
| 96 | + wpversion = response.body:match(pattern) |
| 97 | + 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) |
| 107 | + end |
| 108 | + 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 |
| 121 | + return output_tab |
| 122 | + end |
| 123 | +end |
0 commit comments