NSE LIB

Back to library
Unofficial safe Default

http-wordpress-info

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.

Ports

Any

Protocols

n/a

Attribution

Peter Hill <peter@hackertarget.com> (upstream: hackertarget/nmap-nse-scripts)

Usage

Copy the command and adjust the target or script arguments as needed.

nmap --script http-wordpress-info [--script-args http-wordpress-info.path=<path>,http-wordpress-info.redirects=<number>,...] <host>
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

Overview

Imported from the upstream repository hackertarget/nmap-nse-scripts. 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