diff --git a/scripts/http-slowloris-check.nse b/scripts/http-slowloris-check.nse index f45d37314..1a48b38be 100644 --- a/scripts/http-slowloris-check.nse +++ b/scripts/http-slowloris-check.nse @@ -21,6 +21,11 @@ If second connection gets a timeout 10 or more seconds after the first one, we can conclude that sending additional header prolonged its timeout and that the server is vulnerable to slowloris DoS attack. +A "LIKELY VULNERABLE" result means a server is subject to timeout-extension +attack, but depending on the http server's architecture and resource limits, a +full denial-of-service is not always possible. Complete testing requires +triggering the actual DoS condition and measuring server responsiveness. + You can specify custom http User-agent field with http.useragent script argument. @@ -41,15 +46,17 @@ Idea from Qualys blogpost: -- | http-slowloris-check: -- | VULNERABLE: -- | Slowloris DOS attack --- | State: VULNERABLE --- | Description: --- | Slowloris tries to keep many connections to the target web server open and hold them open as long as possible. --- | It accomplishes this by opening connections to the target web server and sending a partial request. By doing --- | so, it starves the http server's resources causing Denial Of Service. +-- | State: LIKELY VULNERABLE +-- | IDs: CVE:CVE-2007-6750 +-- | Slowloris tries to keep many connections to the target web server open and hold +-- | them open as long as possible. It accomplishes this by opening connections to +-- | the target web server and sending a partial request. By doing so, it starves +-- | the http server's resources causing Denial Of Service. -- | -- | Disclosure date: 2009-09-17 -- | References: --- |_ http://ha.ckers.org/slowloris/ +-- | http://ha.ckers.org/slowloris/ +-- |_ http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-6750 author = "Aleksandar Nikolic" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" @@ -58,63 +65,19 @@ categories = {"vuln", "safe"} portrule = shortport.http -local HalfHTTP -local Bestopt -local TimeWithout -- time without additional headers -local TimeWith -- time with additional headers - --- does a half http request and waits until timeout -local function slowThread1(host,port) - -- if no response was received when determining SSL - if ( Bestopt == "none" ) then - return - end - local socket,status - local catch = function() - TimeWithout = nmap.clock() - end - local try = nmap.new_try(catch) - socket = nmap.new_socket() - socket:set_timeout(500 * 1000) - socket:connect(host.ip, port, Bestopt) - socket:send(HalfHTTP) - try(socket:receive()) - TimeWithout = nmap.clock() -end - --- does a half http request but sends another --- header value after 10 seconds -local function slowThread2(host,port) - -- if no response was received when determining SSL - if ( Bestopt == "none" ) then - return - end - local socket,status - local catch = function() - -- note the time the socket timedout - TimeWith = nmap.clock() - stdnse.debug1("2 try") - end - local try = nmap.new_try(catch) - socket = nmap.new_socket() - socket:set_timeout(500 * 1000) - socket:connect(host.ip, port, Bestopt) - socket:send(HalfHTTP) - stdnse.sleep(10) - socket:send("X-a: b\r\n") - try(socket:receive()) - TimeWith = nmap.clock() -end - action = function(host,port) local slowloris = { title = "Slowloris DOS attack", description = [[ -Slowloris tries to keep many connections to the target web server open and hold them open as long as possible. -It accomplishes this by opening connections to the target web server and sending a partial request. By doing -so, it starves the http server's resources causing Denial Of Service. +Slowloris tries to keep many connections to the target web server open and hold +them open as long as possible. It accomplishes this by opening connections to +the target web server and sending a partial request. By doing so, it starves +the http server's resources causing Denial Of Service. ]], + IDS = { + CVE = 'CVE-2007-6750', + }, references = { 'http://ha.ckers.org/slowloris/', }, @@ -127,15 +90,55 @@ so, it starves the http server's resources causing Denial Of Service. local report = vulns.Report:new(SCRIPT_NAME, host, port) slowloris.state = vulns.STATE.NOT_VULN - local _ - _, _, Bestopt = comm.tryssl(host, port, "GET / \r\n\r\n", {}) -- first determine if we need ssl - HalfHTTP = "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" .. + local sd, response, Bestopt = comm.tryssl(host, port, "GET / \r\n\r\n") -- first determine if we need ssl + if Bestopt == "none" then + stdnse.debug1("Error determining SSL: %s", response) + return nil + end + local HalfHTTP = ( + "POST /" .. tostring(math.random(100000, 900000)) .. " HTTP/1.1\r\n" .. "Host: " .. host.ip .. "\r\n" .. - "User-Agent: " .. http.USER_AGENT .. "\r\n; " .. + "User-Agent: " .. http.USER_AGENT .. "\r\n" .. "Content-Length: 42\r\n" + ) + local TimeWithout -- time without additional headers + + -- does a half http request and waits until timeout + local function slowThread1() + local socket = nmap.new_socket() + local try = nmap.new_try(function() + TimeWithout = nmap.clock() + socket:close() + end) + try(socket:connect(host, port, Bestopt)) + try(socket:send(HalfHTTP)) + socket:set_timeout(500 * 1000) + try(socket:receive()) + TimeWithout = nmap.clock() + end + + local TimeWith -- time with additional headers + + -- does a half http request but sends another + -- header value after 10 seconds + local function slowThread2() + local socket = nmap.new_socket() + local try = nmap.new_try(function() + TimeWith = nmap.clock() + socket:close() + end) + try(socket:connect(host, port, Bestopt)) + try(socket:send(HalfHTTP)) + stdnse.sleep(10) + try(socket:send("X-a: b\r\n")) + socket:set_timeout(500 * 1000) + try(socket:receive()) + TimeWith = nmap.clock() + end + -- both threads run at the same time - local thread1 = stdnse.new_thread(slowThread1, host, port) - local thread2 = stdnse.new_thread(slowThread2, host, port) + local thread1 = stdnse.new_thread(slowThread1) + local thread2 = stdnse.new_thread(slowThread2) while true do -- wait for both threads to die if coroutine.status(thread1) == "dead" and coroutine.status(thread2) == "dead" then break @@ -144,7 +147,8 @@ so, it starves the http server's resources causing Denial Of Service. end -- compare times if ( not(TimeWith) or not(TimeWithout) ) then - return + stdnse.debug1("Unable to time responses: thread died early.") + return nil end local diff = TimeWith - TimeWithout stdnse.debug1("Time difference is: %d",diff) @@ -152,8 +156,7 @@ so, it starves the http server's resources causing Denial Of Service. -- it means that sending additional data prolonged the connection's time -- and the server is vulnerable to slowloris attack if diff >= 10 then - stdnse.debug1("Difference is greater or equal to 10 seconds.") - slowloris.state = vulns.STATE.VULN + slowloris.state = vulns.STATE.LIKELY_VULN end return report:make_output(slowloris) end