mirror of
https://github.com/nmap/nmap.git
synced 2025-12-07 21:21:31 +00:00
Accuracy and speed improvements to irc-unrealircd-backdoor.nse. The main
problem was that the timer was including the time taken by the server to do reverse-DNS resolution and an ident lookup, before it handled the magic "AB" sleep command. So if a server took 10 seconds to time out the ident lookup, the time recorded would be 10 seconds greater than it should have been. If the timeout was long enough, as server would be reported as vulnerable even if not. To compensate for this, the delay was set high, but false positives were still possible and this slowed down the script. First, remove the mutex that allowed only one instance of the script to run at a time. The mutex was meant to provide more accurate timing, but it wasn't really needed because scritp parallelism wasn't the cause of inaccuracy. Next, make sure the server is done sending its initialization messages (and hence is done with its timeouts) before sending the magic "AB" sleep command and starting the timer. This is done by sending an innocuous TIME command immediately upon connection. This is partly because comm.tryssl has to send something, and partly because we can detect when the server has processed the TIME command by looking for a message like ":hostname 451 TIME :You have not registered". Once this is done, we start the timer and send the "AB" command. If we haven't gotten a response to TIME in 60 seconds, go ahead and send the "AB" command anyway. Finally, reduce the delay to 8 seconds from 25. The delay only has to be long enough to overcome any network delay, now that confounding timeouts have been eliminated. In a test of around 600 hosts, the only times I recorded were 0, 1, 8, and 9 seconds, so this looks like a good enough safety margin.
This commit is contained in:
@@ -51,22 +51,32 @@ require "comm"
|
|||||||
require "stdnse"
|
require "stdnse"
|
||||||
|
|
||||||
portrule = shortport.port_or_service({6666,6667,6697,6679,8067},{"irc","ircs"})
|
portrule = shortport.port_or_service({6666,6667,6697,6679,8067},{"irc","ircs"})
|
||||||
mutex = nmap.mutex(nmap)
|
|
||||||
|
|
||||||
|
|
||||||
action = function(host, port)
|
action = function(host, port)
|
||||||
local socket = nmap.new_socket()
|
local socket = nmap.new_socket()
|
||||||
local code, message
|
local code, message
|
||||||
local status, err
|
local status, err
|
||||||
local data = ""
|
local data
|
||||||
local delay = 25
|
-- Wait up to this long for the server to send its startup messages and
|
||||||
local timeout = 35000
|
-- 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.
|
-- If the command takes (delay - delay_fudge) or more seconds, the server is vulnerable.
|
||||||
-- I defined the furdge as 1 second, for now, just because of rounding issues. In practice,
|
-- I defined the furdge 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.
|
-- the actual delay should never be shorter than the given delay, only longer.
|
||||||
local delay_fudge = 1
|
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.
|
-- The 'AB' sequence triggers the backdoor to run a command.
|
||||||
local trigger = "AB"
|
local trigger = "AB"
|
||||||
|
|
||||||
@@ -105,51 +115,64 @@ action = function(host, port)
|
|||||||
stdnse.sleep(waittime)
|
stdnse.sleep(waittime)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get a lock on the mutex (this script can't run in parallel because it requires timings)
|
-- Send an innocuous command as fodder for tryssl.
|
||||||
mutex "lock"
|
stdnse.print_debug(1, "irc-unrealircd-backdoor: Sending command: %s", noop_command);
|
||||||
-- stdnse.print_debug(1, "irc-unrealircd-backdoor: Mutex locked")
|
local socket, response = comm.tryssl(host, port, noop_command .. "\n", {recv_before=false})
|
||||||
|
|
||||||
-- Send out our command
|
|
||||||
stdnse.print_debug(1, "irc-unrealircd-backdoor: Sending command: %s", full_command);
|
|
||||||
local socket, response = comm.tryssl(host, port, full_command .. "\n", {timeout=timeout, recv_before=false})
|
|
||||||
|
|
||||||
-- Make sure the socket worked
|
-- Make sure the socket worked
|
||||||
if(not(socket) or not(response)) then
|
if(not(socket) or not(response)) then
|
||||||
stdnse.print_debug(1, "irc-unrealircd-backdoor: Couldn't connect to remote host")
|
stdnse.print_debug(1, "irc-unrealircd-backdoor: Couldn't connect to remote host")
|
||||||
-- stdnse.print_debug(1, "irc-unrealircd-backdoor: Freeing mutex")
|
return nil
|
||||||
mutex "done"
|
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.print_debug(1, "irc-unrealircd-backdoor: Receive failed after %s: %s", noop_command, response)
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Send the backdoor command.
|
||||||
|
stdnse.print_debug(1, "irc-unrealircd-backdoor: Sending command: %s", full_command);
|
||||||
|
status, err = socket:send(full_command .. "\n")
|
||||||
|
if not status then
|
||||||
|
stdnse.print_debug(1, "irc-unrealircd-backdoor: Send failed: %s", err)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get the current time so we can measure the delay
|
-- Get the current time so we can measure the delay
|
||||||
-- Note: This has to be AFTER comm.tryssl(), otherwise we run into false positives.
|
time = os.time(os.date('*t'))
|
||||||
-- See http://seclists.org/nmap-dev/2010/q2/923
|
socket:set_timeout((delay + 5) * 1000)
|
||||||
local time = os.time(os.date('*t'))
|
|
||||||
|
|
||||||
-- Accumulate the response in the 'data' string
|
-- Accumulate the response in the 'data' string
|
||||||
data = data .. response
|
status = true
|
||||||
while(not(string.find(data, unique))) do
|
data = ""
|
||||||
--io.write("Response: " .. response)
|
while not string.find(data, unique) do
|
||||||
status, response = socket:receive()
|
status, response = socket:receive_bytes(0)
|
||||||
|
if status then
|
||||||
if(status) then
|
|
||||||
-- Keep adding to the 'data' string
|
|
||||||
data = data .. response
|
data = data .. response
|
||||||
else
|
else
|
||||||
-- If the server unexpectedly closes the connection, it is usually related to throttling.
|
-- If the server unexpectedly closes the connection, it
|
||||||
-- Therefore, we print a throttling warning.
|
-- is usually related to throttling. Therefore, we
|
||||||
|
-- print a throttling warning.
|
||||||
stdnse.print_debug(1, "irc-unrealircd-backdoor: Receive failed: %s", response)
|
stdnse.print_debug(1, "irc-unrealircd-backdoor: Receive failed: %s", response)
|
||||||
socket:close()
|
socket:close()
|
||||||
-- stdnse.print_debug(1, "irc-unrealircd-backdoor: Freeing mutex")
|
|
||||||
mutex "done"
|
|
||||||
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)."
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Free the mutex so other scripts can get going
|
|
||||||
-- stdnse.print_debug(1, "irc-unrealircd-backdoor: Freeing mutex")
|
|
||||||
mutex "done"
|
|
||||||
|
|
||||||
-- Determine the elapsed time
|
-- Determine the elapsed time
|
||||||
local elapsed = os.time(os.date('*t')) - time
|
local elapsed = os.time(os.date('*t')) - time
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user