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", } }