diff --git a/CHANGELOG b/CHANGELOG
index 894c5c20b..805b3b524 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added http-slowloris-check script which checks if the server is vulnerable
+ to a Slowloris DoS attack in a safe way. [Aleksandar Nikolic]
+
o Removed pos_scan scan engine as the old implementation of RPC grind was the
last scan type to use it. [Hani Benhabiles]
diff --git a/nselib/http.lua b/nselib/http.lua
index 281e7286d..d960e3760 100644
--- a/nselib/http.lua
+++ b/nselib/http.lua
@@ -117,7 +117,7 @@ _ENV = stdnse.module("http", stdnse.seeall)
---Use ssl if we have it
local have_ssl, openssl = pcall(require,'openssl')
-local USER_AGENT = stdnse.get_script_args('http.useragent') or "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
+USER_AGENT = stdnse.get_script_args('http.useragent') or "Mozilla/5.0 (compatible; Nmap Scripting Engine; http://nmap.org/book/nse.html)"
local MAX_REDIRECT_COUNT = 5
-- Recursively copy a table.
diff --git a/scripts/http-slowloris-check.nse b/scripts/http-slowloris-check.nse
new file mode 100644
index 000000000..28cf7962e
--- /dev/null
+++ b/scripts/http-slowloris-check.nse
@@ -0,0 +1,147 @@
+local coroutine = require "coroutine"
+local math = require "math"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local comm = require "comm"
+local vulns = require "vulns"
+local http = require "http"
+
+
+description = [[
+Tests a web server for vulnerability to the Slowloris DoS attack.
+
+Slowloris was described at Defcon 17 by RSnake
+(see http://ha.ckers.org/slowloris/).
+
+This script opens two connections to the server, each without
+the final CRLF. After 10 seconds, second connection sends
+additional header. Both connections then wait for server timeout.
+If second connection gets a timeout 10 or more seconds after the
+first one, we can conclude that sending additional header prolonged
+it's timeout and that the server is vulnerable to slowloris DoS attack.
+
+You can specify custom http User-agent field with http.useragent
+script argument.
+
+Idea from Qualys blogpost:
+ * https://community.qualys.com/blogs/securitylabs/2011/07/07/identifying-slow-http-attack-vulnerabilities-on-web-applications
+
+]]
+
+---
+-- @usage
+-- nmap --script http-slowloris-check
+--
+-- @args http.useragent Specifies custom user agent string.
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 80/tcp open http syn-ack
+-- | 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.
+-- |
+-- | Disclosure date: 2009-09-17
+-- | References:
+-- |_ http://ha.ckers.org/slowloris/
+
+author = "Aleksandar Nikolic"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+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)
+ 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)
+ local socket,status
+ local catch = function()
+ -- note the time the socket timedout
+ TimeWith = nmap.clock()
+ stdnse.print_debug("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.
+ ]],
+ references = {
+ 'http://ha.ckers.org/slowloris/',
+ },
+ dates = {
+ disclosure = {year = '2009', month = '09', day = '17'},
+ },
+ exploit_results = {},
+ }
+
+ local report = vulns.Report:new(SCRIPT_NAME, host, port)
+ slowloris.state = vulns.STATE.NOT_VULN
+
+ _, _, 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" ..
+ "Host: " .. host.ip .. "\r\n" ..
+ "User-Agent: " .. http.USER_AGENT .. "\r\n; " ..
+ "Content-Length: 42\r\n"
+ -- both threads run at the same time
+ local thread1 = stdnse.new_thread(slowThread1, host, port)
+ local thread2 = stdnse.new_thread(slowThread2, host, port)
+ while true do -- wait for both threads to die
+ if coroutine.status(thread1) == "dead" and coroutine.status(thread2) == "dead" then
+ break
+ end
+ stdnse.sleep(1)
+ end
+ -- compare times
+ local diff = TimeWith - TimeWithout
+ stdnse.print_debug("Time difference is: %d",diff)
+ -- if second connection died 10 or more seconds after the first
+ -- it means that sending additional data prolonged the connection's time
+ -- and the server is vulnerable to slowloris attack
+ if diff >= 10 then
+ stdnse.print_debug("Difference is greater or equal to 10 seconds.")
+ slowloris.state = vulns.STATE.VULN
+ end
+ return report:make_output(slowloris)
+end
diff --git a/scripts/script.db b/scripts/script.db
index dacd57993..eb15cc6f2 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -30,8 +30,8 @@ Entry { filename = "broadcast-dhcp-discover.nse", categories = { "broadcast", "s
Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dns-service-discovery.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-dropbox-listener.nse", categories = { "broadcast", "safe", } }
-Entry { filename = "broadcast-eigrp-discovery.nse", categories = { "discovery", "broadcast", "safe", } }
-Entry { filename = "broadcast-igmp-discovery.nse", categories = { "discovery", "safe", "broadcast", } }
+Entry { filename = "broadcast-eigrp-discovery.nse", categories = { "broadcast", "discovery", "safe", } }
+Entry { filename = "broadcast-igmp-discovery.nse", categories = { "broadcast", "discovery", "safe", } }
Entry { filename = "broadcast-listener.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-ms-sql-discover.nse", categories = { "broadcast", "safe", } }
Entry { filename = "broadcast-netbios-master-browser.nse", categories = { "broadcast", "safe", } }
@@ -180,6 +180,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-check.nse", categories = { "safe", "vuln", } }
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", } }
@@ -251,8 +252,8 @@ Entry { filename = "modbus-discover.nse", categories = { "discovery", "intrusive
Entry { filename = "mongodb-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "mongodb-databases.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "mongodb-info.nse", categories = { "default", "discovery", "safe", } }
-Entry { filename = "mrinfo.nse", categories = { "discovery", "safe", "broadcast"} }
-Entry { filename = "mtrace.nse", categories = { "discovery", "safe", "broadcast"} }
+Entry { filename = "mrinfo.nse", categories = { "broadcast", "discovery", "safe", } }
+
Entry { filename = "ms-sql-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "ms-sql-config.nse", categories = { "discovery", "safe", } }
Entry { filename = "ms-sql-dac.nse", categories = { "discovery", "safe", } }
@@ -263,6 +264,7 @@ Entry { filename = "ms-sql-info.nse", categories = { "default", "discovery", "sa
Entry { filename = "ms-sql-query.nse", categories = { "discovery", "safe", } }
Entry { filename = "ms-sql-tables.nse", categories = { "discovery", "safe", } }
Entry { filename = "ms-sql-xp-cmdshell.nse", categories = { "intrusive", } }
+Entry { filename = "mtrace.nse", categories = { "broadcast", "discovery", "safe", } }
Entry { filename = "mysql-audit.nse", categories = { "discovery", "safe", } }
Entry { filename = "mysql-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "mysql-databases.nse", categories = { "discovery", "intrusive", } }