summaryrefslogtreecommitdiffstats
path: root/scripts/irc-sasl-brute.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/irc-sasl-brute.nse')
-rw-r--r--scripts/irc-sasl-brute.nse204
1 files changed, 204 insertions, 0 deletions
diff --git a/scripts/irc-sasl-brute.nse b/scripts/irc-sasl-brute.nse
new file mode 100644
index 0000000..90acbfc
--- /dev/null
+++ b/scripts/irc-sasl-brute.nse
@@ -0,0 +1,204 @@
+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
+
+