Web Server 7 Meets Slowloris
By jyri on Jun 29, 2009
Lately there's been some noise about slowloris, a perl script which sends HTTP requests slowly. While there's nothing new about this technique, I've been asked about it a few times so I wanted to show how easy it is to protect against it if you're lucky enough to be using Sun's Web Server 7.
In a nutshell, the script opens a connection to the target web server and sends valid request headers and then continues to send more headers, slowly. Specifically, it first sends:
GET / HTTP/1.1 Host: $hostname 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 .0.4506.2152; .NET CLR 3.5.30729; MSOffice 12) Content-Length: 42 X-a: b
Then it continues to send:
after every $timeout delay. It has a default $timeout of 100 seconds but you can change this with -timeout switch.
Let's look at the more general cases here instead of just slowloris specifically.
The most rudimentary form of this attack is to open a connection to the web server and either don't send anything or send a partial request and nothing else after that (as described above, this is not what slowloris does).
You'll want your web server to eventually time out and close the connection if this happens. In Web Server 7 this is controlled by the io-timeout element in server.xml. The default value is 30 (seconds). Let's try it:
% date;telnet localhost 80;date Mon Jun 29 19:05:43 PDT 2009 Trying 127.0.0.1... Connected to localhost. Escape character is '\^]'. Connection to localhost closed by foreign host. Mon Jun 29 19:06:14 PDT 2009
As you can see, 31 seconds went by before the connection was closed. You can change io-timeout to be shorter if you wish:
<http> <io-timeout>15</io-timeout> </http> % date;telnet localhost 8080;date Mon Jun 29 19:15:12 PDT 2009 Trying 127.0.0.1... Connected to localhost. Escape character is '\^]'. Connection to localhost closed by foreign host. Mon Jun 29 19:15:27 PDT 2009
Above I changed the io-timeout to 15 and indeed it took 15 seconds before closing the mute connection. Let's try the same thing but send a partial request:
% date;telnet localhost 8080;date Mon Jun 29 19:14:38 PDT 2009 Trying 127.0.0.1... Connected to localhost. Escape character is '\^]'. GET / HTTP/1.1 Host: localhost HTTP/1.1 408 Request Timeout Server: Sun-Java-System-Web-Server/7.0 Date: Tue, 30 Jun 2009 02:14:54 GMT Content-length: 148 Content-type: text/html Connection: close <HTML><HEAD><TITLE>Request Timeout</TITLE></HEAD> <BODY><H1>Request Timeout</H1> The server timed out waiting for the client request. </BODY></HTML>Connection to localhost closed by foreign host. Mon Jun 29 19:14:54 PDT 2009
Ok, let's try to make the attack more interesting. Instead of just going silent, the client can continue sending more request data, just slowly. This is what slowloris does. As long as the client sends a little bit of valid request data often enough to not get disconnected by the timeout it can hold on to the connection.
Fortunately Web Server 7 also monitors the time it takes to receive all the request headers. This can be configured using the request-header-timeout element in server.xml. This can be used to defeat a slowloris-type attack. Even thought the slowloris request never actually completes (since it just keeps sending more headers forever), Web Server 7 will stop waiting and close it off after request-header-timeout seconds go by.
<http> <request-header-timeout>5</request-header-timeout> </http>
Of course, if you set request-header-timeout to 5s you could then run slowloris with a -timeout of less than 5 seconds. However, this quickly starts to defeat the premise of this style of attack. The idea behind a slowloris-style attack is to attempt to tie up the web server quietly without the client having to generate hundreds or more connections per second. For fun, I set my request-header-timeout to 1s and ran slowloris with a -timeout of 1s. The result is the client machine uses up all its CPU generating new connections while Web Server 7 continues to be happily responsive.
A variant of this attack is to send a POST request, send all the request headers and then start to send the body data, slowly. Note that slowloris does not implement this (the -httpready flag sends a POST instead of a GET, but it continues to send X-a:b request headers, not request body data). However it is easy enough to write a tool to do this instead.
If you encounter that scenario you're in luck because Web Server 7 also monitors the time for the request body to arrive and you can set a timeout on that as well, using the request-body-timeout element:
<http> <request-body-timeout>5</request-body-timeout> </http>
That's all there is to it to protect against slowlaris and similar slow-client attacks if you're using Sun Web Server 7! Enjoy!