mirror of
https://github.com/nmap/nmap.git
synced 2025-12-10 09:49:05 +00:00
Add irc-botnet-channels.nse.
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Add irc-botnet-channels.nse, to check an IRC server for
|
||||
channel names that may be used by botnets. [David, Ange Gutek]
|
||||
|
||||
o [NSE] Added the http-method-tamper script that detects authentication bypass
|
||||
vulnerabilities using the http HEAD method as reported in CVE-2010-738.
|
||||
[Hani Benhabiles]
|
||||
|
||||
316
scripts/irc-botnet-channels.nse
Normal file
316
scripts/irc-botnet-channels.nse
Normal file
@@ -0,0 +1,316 @@
|
||||
description = [[
|
||||
Checks an IRC server for channels that may be used by botnets.
|
||||
|
||||
Control the list of channel names with the <code>irc-botnet-channels.channels</code>
|
||||
script argument. The default list of channels is
|
||||
* loic
|
||||
* Agobot
|
||||
* Slackbot
|
||||
* Mytob
|
||||
* Rbot
|
||||
* SdBot
|
||||
* poebot
|
||||
* IRCBot
|
||||
* VanBot
|
||||
* MPack
|
||||
* Storm
|
||||
* GTbot
|
||||
* Spybot
|
||||
* Phatbot
|
||||
* Wargbot
|
||||
* RxBot
|
||||
]]
|
||||
|
||||
author = "David Fifield, Ange Gutek"
|
||||
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
|
||||
categories = {"discovery", "vuln", "safe"}
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap -p 6667 --script=irc-botnet-channels <target>
|
||||
-- @usage
|
||||
-- nmap -p 6667 --script=irc-botnet-channels --script-args 'irc-botnet-channels.channels={chan1,chan2,chan3}' <target>
|
||||
--
|
||||
-- @output
|
||||
-- | irc-botnet-channels:
|
||||
-- | #loic
|
||||
-- |_ #RxBot
|
||||
|
||||
require("stdnse")
|
||||
require "shortport"
|
||||
require("nsedebug")
|
||||
require("comm")
|
||||
|
||||
-- See RFC 2812 for protocol documentation.
|
||||
|
||||
-- Section 5.1 for protocol replies.
|
||||
local RPL_TRYAGAIN = "263"
|
||||
local RPL_LIST = "322"
|
||||
local RPL_LISTEND = "323"
|
||||
|
||||
local DEFAULT_CHANNELS = {
|
||||
"loic",
|
||||
"Agobot",
|
||||
"Slackbot",
|
||||
"Mytob",
|
||||
"Rbot",
|
||||
"SdBot",
|
||||
"poebot",
|
||||
"IRCBot",
|
||||
"VanBot",
|
||||
"MPack",
|
||||
"Storm",
|
||||
"GTbot",
|
||||
"Spybot",
|
||||
"Phatbot",
|
||||
"Wargbot",
|
||||
"RxBot",
|
||||
}
|
||||
|
||||
portrule = shortport.port_or_service({6666, 6667, 6697, 6679}, {"irc", "ircs"})
|
||||
|
||||
-- Parse an IRC message. Returns nil, errmsg in case of error. Otherwise returns
|
||||
-- true, prefix, command, params. prefix may be nil. params is an array of
|
||||
-- strings. The final param has the ':' stripped from the beginning.
|
||||
--
|
||||
-- The special return value true, nil indicates an empty message to be ignored.
|
||||
--
|
||||
-- See RFC 2812, section 2.3.1 for BNF of a message.
|
||||
local function irc_parse_message(s)
|
||||
local prefix, command, params
|
||||
local _, p, t
|
||||
|
||||
s = string.gsub(s, "\r?\n$", "")
|
||||
if string.match(s, "^ *$") then
|
||||
return true, nil
|
||||
end
|
||||
|
||||
p = 0
|
||||
_, t, prefix = string.find(s, "^:([^ ]+) +", p + 1)
|
||||
if t then
|
||||
p = t
|
||||
end
|
||||
|
||||
-- We do not check for any special format of the command name or
|
||||
-- number.
|
||||
_, p, command = string.find(s, "^([^ ]+)", p + 1)
|
||||
if not p then
|
||||
return nil, "Presumed message is missing a command."
|
||||
end
|
||||
|
||||
params = {}
|
||||
while p + 1 <= #s do
|
||||
local param
|
||||
|
||||
_, p = string.find(s, "^ +", p + 1)
|
||||
if not p then
|
||||
return nil, "Missing a space before param."
|
||||
end
|
||||
-- We don't do any checks on the contents of params.
|
||||
if #params == 14 then
|
||||
params[#params + 1] = string.sub(s, p + 1)
|
||||
break
|
||||
elseif string.match(s, "^:", p + 1) then
|
||||
params[#params + 1] = string.sub(s, p + 2)
|
||||
break
|
||||
else
|
||||
_, p, param = string.find(s, "^([^ ]+)", p + 1)
|
||||
if not p then
|
||||
return nil, "Missing a param."
|
||||
end
|
||||
params[#params + 1] = param
|
||||
end
|
||||
end
|
||||
|
||||
return true, prefix, command, params
|
||||
end
|
||||
|
||||
local function irc_compose_message(prefix, command, ...)
|
||||
local parts, params
|
||||
|
||||
parts = {}
|
||||
if prefix then
|
||||
parts[#parts + 1] = prefix
|
||||
end
|
||||
|
||||
if string.match(command, "^:") then
|
||||
return nil, "Command may not begin with ':'."
|
||||
end
|
||||
parts[#parts + 1] = command
|
||||
|
||||
params = {...}
|
||||
for i, param in ipairs(params) do
|
||||
if not string.match(param, "^[^%z\r\n :][^%z\r\n ]*$") then
|
||||
if i < #params then
|
||||
return nil, "Bad format for param."
|
||||
else
|
||||
parts[#parts + 1] = ":" .. param
|
||||
end
|
||||
else
|
||||
parts[#parts + 1] = param
|
||||
end
|
||||
end
|
||||
|
||||
return stdnse.strjoin(" ", parts) .. "\r\n"
|
||||
end
|
||||
|
||||
local function random_nick()
|
||||
local nick = {}
|
||||
|
||||
for i = 1, 9 do
|
||||
nick[#nick + 1] = string.char(math.random(string.byte("a"), string.byte("z")))
|
||||
end
|
||||
|
||||
return table.concat(nick)
|
||||
end
|
||||
|
||||
local function splitlines(s)
|
||||
local lines = {}
|
||||
local _, i, j
|
||||
|
||||
i = 1
|
||||
while i <= #s do
|
||||
_, j = string.find(s, "\r?\n", i)
|
||||
lines[#lines + 1] = string.sub(s, i, j)
|
||||
if not j then
|
||||
break
|
||||
end
|
||||
i = j + 1
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
local function irc_connect(host, port, nick, user, pass)
|
||||
local commands = {}
|
||||
local irc = {}
|
||||
local banner
|
||||
|
||||
-- Section 3.1.1.
|
||||
if pass then
|
||||
commands[#commands + 1] = irc_compose_message(nil, "PASS", pass)
|
||||
end
|
||||
nick = nick or random_nick()
|
||||
commands[#commands + 1] = irc_compose_message(nil, "NICK", nick)
|
||||
user = user or nick
|
||||
commands[#commands + 1] = irc_compose_message(nil, "USER", user, "8", "*", user)
|
||||
|
||||
irc.sd, banner = comm.tryssl(host, port, table.concat(commands))
|
||||
if not irc.sd then
|
||||
return nil, "Unable to open connection."
|
||||
end
|
||||
|
||||
irc.sd:set_timeout(60 * 1000)
|
||||
|
||||
-- Buffer these initial lines for irc_readline.
|
||||
irc.linebuf = splitlines(banner)
|
||||
|
||||
irc.buf = stdnse.make_buffer(irc.sd, "\r?\n")
|
||||
|
||||
return irc
|
||||
end
|
||||
|
||||
local function irc_disconnect(irc)
|
||||
irc.sd:close()
|
||||
end
|
||||
|
||||
local function irc_readline(irc)
|
||||
local line
|
||||
|
||||
if next(irc.linebuf) then
|
||||
line = table.remove(irc.linebuf, 1)
|
||||
if string.match(line, "\r?\n$") then
|
||||
return line
|
||||
else
|
||||
-- We had only half a line buffered.
|
||||
return line .. irc.buf()
|
||||
end
|
||||
else
|
||||
return irc.buf()
|
||||
end
|
||||
end
|
||||
|
||||
local function irc_read_message(irc)
|
||||
local line, err
|
||||
|
||||
line, err = irc_readline(irc)
|
||||
if not line then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return irc_parse_message(line)
|
||||
end
|
||||
|
||||
local function irc_send_message(irc, prefix, command, ...)
|
||||
local line
|
||||
|
||||
line = irc_compose_message(prefix, command, ...)
|
||||
irc.sd:send(line)
|
||||
end
|
||||
|
||||
-- Prefix channel names with '#' if necessary and concatenate into a
|
||||
-- comma-separated list.
|
||||
local function concat_channel_list(channels)
|
||||
local mod = {}
|
||||
|
||||
for _, channel in ipairs(channels) do
|
||||
if not string.match(channel, "^#") then
|
||||
channel = "#" .. channel
|
||||
end
|
||||
mod[#mod + 1] = channel
|
||||
end
|
||||
|
||||
return stdnse.strjoin(",", mod)
|
||||
end
|
||||
|
||||
function action(host, port)
|
||||
local irc
|
||||
local search_channels
|
||||
local channels
|
||||
local errorparams
|
||||
|
||||
search_channels = stdnse.get_script_args(SCRIPT_NAME .. ".channels")
|
||||
if not search_channels then
|
||||
search_channels = DEFAULT_CHANNELS
|
||||
elseif type(search_channels) == "string" then
|
||||
search_channels = {search_channels}
|
||||
end
|
||||
|
||||
irc = irc_connect(host, port)
|
||||
irc_send_message(irc, "LIST", concat_channel_list(search_channels))
|
||||
|
||||
channels = {}
|
||||
while true do
|
||||
local status, prefix, code, params
|
||||
|
||||
status, prefix, code, params = irc_read_message(irc)
|
||||
if not status then
|
||||
-- Error message from irc_read_message.
|
||||
errorparams = {prefix}
|
||||
break
|
||||
elseif code == "ERROR" then
|
||||
errorparams = params
|
||||
break
|
||||
elseif code == RPL_TRYAGAIN then
|
||||
errorparams = params
|
||||
break
|
||||
elseif code == RPL_LIST then
|
||||
if #params >= 2 then
|
||||
channels[#channels + 1] = params[2]
|
||||
else
|
||||
stdnse.print_debug("Got short " .. RPL_LIST .. "response.")
|
||||
end
|
||||
elseif code == RPL_LISTEND then
|
||||
break
|
||||
end
|
||||
end
|
||||
irc_disconnect(irc)
|
||||
|
||||
if errorparams then
|
||||
channels[#channels + 1] = "ERROR: " .. stdnse.strjoin(" ", errorparams)
|
||||
end
|
||||
|
||||
return stdnse.format_output(true, channels)
|
||||
end
|
||||
@@ -131,6 +131,7 @@ Entry { filename = "ip-geolocation-ipinfodb.nse", categories = { "discovery", "e
|
||||
Entry { filename = "ip-geolocation-maxmind.nse", categories = { "discovery", "external", "safe", } }
|
||||
Entry { filename = "ipidseq.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "ipv6-node-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "irc-botnet-channels.nse", categories = { "discovery", "safe", "vuln", } }
|
||||
Entry { filename = "irc-brute.nse", categories = { "brute", "intrusive", } }
|
||||
Entry { filename = "irc-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "irc-unrealircd-backdoor.nse", categories = { "exploit", "intrusive", "malware", "vuln", } }
|
||||
|
||||
Reference in New Issue
Block a user