coolbins/system/usr/share/nmap/scripts/irc-sasl-brute.nse

205 lines
6.2 KiB
Lua

local base64 = require "base64"
local brute = require "brute"
local comm = require "comm"
local creds = require "creds"
local sasl = require "sasl"
local irc = require "irc"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
description=[[
Performs brute force password auditing against IRC (Internet Relay Chat) servers supporting SASL authentication.
]]
-- You can read more about sasl here:
-- https://github.com/atheme/charybdis/blob/master/doc/sasl.txt
-- http://www.leeh.co.uk/draft-mitchell-irc-capabilities-02.html
-- the first link also explains the meaning of constants used in
-- this script.
---
-- @usage
-- nmap --script irc-sasl-brute -p 6667 <ip>
--
-- @output
-- PORT STATE SERVICE REASON
-- 6667/tcp open irc syn-ack
-- | irc-sasl-brute:
-- | Accounts
-- | root:toor - Valid credentials
-- | Statistics
-- |_ Performed 60 guesses in 29 seconds, average tps: 2
--
-- @args irc-sasl-brute.threads the number of threads to use while brute-forcing.
-- Defaults to 2.
author = "Piotr Olma"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories={"brute","intrusive"}
portrule = irc.portrule
local dbg = stdnse.debug
-- some parts of the following class are taken from irc-brute written by Patrik
Driver = {
new = function(self, host, port, saslencoder)
local o = { host = host, port = port, saslencoder = saslencoder}
setmetatable(o, self)
self.__index = self
return o
end,
connect = function(self)
-- the high timeout should take delays from ident into consideration
local s, r, opts, _ = comm.tryssl(self.host,
self.port,
"CAP REQ sasl\r\n",
{ timeout = 10000 } )
if ( not(s) ) then
return false, "Failed to connect to server"
end
if string.find(r:lower(), "throttled") then
-- we were reconnecting too fast
dbg(2, "throttled.")
return false, "We got throttled."
end
local status, _ = s:send("CAP END\r\n")
if not status then return false, "Send failed." end
local response
repeat
status, response = s:receive_lines(1)
if not status then return false, "Receive failed." end
if string.find(response, "ACK") then status = false end
until (not status)
self.socket = s
return true
end,
login = function(self, username, password)
self.socket:send("AUTHENTICATE ".. self.saslencoder:get_mechanism() .."\r\n")
local status, response, challenge
repeat
status, response = self.socket:receive_lines(1)
if not status then
local err = brute.Error:new(response)
err:setRetry(true)
return false, err
end
challenge = string.match(response, "AUTHENTICATE (.*)")
dbg(3, "challenge found: %s", tostring(challenge))
if challenge then status = false end
until (not status)
local msg = self.saslencoder:encode(username, password, challenge)
-- SASL PLAIN is supposed to be plaintext, but freenode actually wants it to be base64 encoded
if self.saslencoder:get_mechanism() == "PLAIN" then
msg = base64.enc(msg)
end
local status, data = self.socket:send("AUTHENTICATE "..msg.."\r\n")
local success = false
if ( not(status) ) then
local err = brute.Error:new( data )
-- This might be temporary, set the retry flag
err:setRetry( true )
return false, err
end
repeat
status, response = self.socket:receive_lines(1)
if ( status and string.find(response, "90[45]") ) then
status = false
end
if ( status and string.find(response, "90[03]") ) then
success = true
status = false
end
until (not status)
if (success) then
return true, creds.Account:new(username, password, creds.State.VALID)
end
return false, brute.Error:new("Incorrect username or password")
end,
disconnect = function(self) return self.socket:close() end,
}
-- checks if server supports sasl authentication and if it does, also checks for supported
-- mechanisms
local function check_sasl(host, port)
local s, r, opts, _ = comm.tryssl(host, port, "CAP REQ sasl\r\n", { timeout = 15000 } )
repeat
local status, lines = s:receive_lines(1)
if string.find(lines, "ACK") then status = false end
if string.find(lines, "NAK") then
s:close()
return false
end
until (not status)
-- we know that sasl is supported, now check which mechanisms can be used
local to_check = {"PLAIN", "DH-BLOWFISH", "NTLM", "CRAM-MD5", "DIGEST-MD5"}
local supported = {}
for _,m in ipairs(to_check) do
s:send("AUTHENTICATE "..m.."\r\n")
dbg(3, "checking mechanism %s", m)
repeat
local status, lines = s:receive_lines(1)
if string.find(lines, "AUTHENTICATE") then
s:send("AUTHENTICATE abort\r\n") -- it's not a real command, just to break the process
-- wait till we get a message indicating failed authentication
repeat
status, lines = s:receive_lines(1)
if string.find(lines, "90[45]") then status = false end
until (not status)
table.insert(supported, m)
status = false
elseif string.find(lines, "90[45]") then
status = false
break
end
until (not status)
end
s:close()
return true, supported
end
action = function(host, port)
local sasl_supported, mechs = check_sasl(host, port)
if not sasl_supported then
return stdnse.format_output(false, "Server doesn't support SASL authentication.")
end
local saslencoder = sasl.Helper:new()
local sasl_mech
-- check if the library supports any of the mechanisms we identified
for _,m in ipairs(mechs) do
if saslencoder:set_mechanism(m) then
sasl_mech = m
dbg(2, "supported mechanism found: %s", m)
break
end
end
local engine = brute.Engine:new(Driver, host, port, saslencoder)
engine.options.script_name = SCRIPT_NAME
engine.options.firstonly = true
-- irc servers seem to be restrictive about too many connection attempts
-- in a short time thus we need to limit the number of threads
local threads = stdnse.get_script_args(("%s.threads"):format(SCRIPT_NAME))
threads = tonumber(threads) or 2
engine:setMaxThreads(threads)
local status, accounts = engine:start()
return accounts
end