1

I'm trying to write a rule to catch a Slow-Loris attack, this is what i have -

alert tcp any any -> any any (msg:"Possible Slow Loris attack"; classtype: denial-of-service; flow: to_server, established; pcre: !"/\x0D\x0A\x0D\x0A$/H"; threshold: type threshold, track by_dst, count 10, seconds 5; sid 3000009; rev:1;)

(I may have messed up the syntax a bit but that's because trying to format it here, i'm positive that the syntax is okay on my end)

I also tried messing with the RE a bit, tried "/\x0D\x0A$/H" and that didn't work either

The Slow-Loris that i tried to write myself and the Slow Loris which i downloaded from somewhere both send packets with "incomplete" HTTP headers, so they don't end with \r\n\r\n like a finished header. But i can't manage to catch either one.

Looked at the packets on Wireshark and they end with 0D 0A

Looked at downloaded.rules and the rule isn't there. Did tail for snort.conf and there are no errors there. Did service nsm status to see maybe the service itself didn't go up correctly, but it's all OK. EDIT - There was a : missing, i fixed it and now it appears on downloaded.rules. still doesn't get caught though

Not really sure what i'm doing wrong, would appreciate some help.

2

It looks like there are a couple of things in your signature that won't work:

  1. Using the /H option in PCRE utilizes the HTTP preprocessor and says that the content needs to be matched against the http_header. When a GET request is parsed by the preprocessor, |0d 0a 0d 0a| signifies the end of the header; which means you cannot search for that inside the header.

  2. When using flow or any HTTP preprocessors, the TCP stream is reassembled (by default, unless otherwise specified). This means that in order to match content in the header, all of the packets that make up the HTTP request need to be observed and the request has to end before the preprocessor will be able to parse all of the parts that make up the request (e.g. http_method, http_uri, http_header).

Honestly, I am not sure what the best approach would be to write a signature to detect this activity [generically] would be, simply because it goes outside of the normal bounds of what Snort is designed to detect.

However, what I would recommend in this situation would be to find all of the publicly available tools for SlowLoris (starting with https://github.com/gkbrk/slowloris.git - available to install via pip3), run them in a testing environment and analyze what actually occurs over the wire.

I did that and this is the traffic that it generates:

GET /?1153 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0
Accept-language: en-US,en,q=0.5
X-a: 1350
X-a: 210
X-a: 518
X-a: 4622
X-a: 2951
X-a: 2666
X-a: 149
X-a: 1756
X-a: 4994
X-a: 2688

This happens on multiple connections up to the maximum number of sockets specified (150 by default). Each connection would be kept alive with a incomplete HTTP GET request by sending an additional X-a header field every so often.

The way I would write a signature to detect this activity would be:

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SlowLoris.py DoS attempt"; \
    flow:established,to_server,no_stream; content:"X-a:"; dsize:<15; \
    detection_filter:track by_dst, count 3, seconds 30; \
    classtype:denial-of-service; sid:1; rev:1; )

event_filter gen_id 1, sig_id 1, type limit, track by_src, count 1, seconds 5

Important things about this rule:

  1. Added no_stream to flow option. This tells the Stream5 preprocessor not to bother checking how the content relates in the context of the reassembled stream. It basically just looks at the packet itself. Important when using the dsize option.
  2. Added dsize:<15. The lines that contain the X-a headers are sent in single packets. I observed a typical packet to look like:

    0000 58 2d 61 3a 20 32 36 38 38 0d 0a X-a: 2688..

    With four random digits for the value, the length of the content is 11 bytes. I just decided to say <15 just in case.

  3. Added detection_filter. I find that a combination of detection_filter in the rule combined with an event_filter gives better control for alert-rate limiting and thresholding. The detection_filter options I specified makes sure to only fire alerts if 3 or more occur from the same source within a 30 second window.
  4. Added an event_filter to limit how many alerts fire once the detection_filter is reached. By limiting alerts to 1 every 5 seconds, alerting will still be loquacious enough to let you know something's going on without flooding you with extraneous alerts.

Below is sample output from live testing using Snort v2.9.9.0 for the duration of 3 minutes

SlowLoris.py

(Python3) λ slowloris -p 80 192.168.126.128
[05-12-2017 13:45:01] Attacking 192.168.126.128 with 150 sockets.
[05-12-2017 13:45:01] Creating sockets...
[05-12-2017 13:45:05] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:45:24] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:45:43] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:46:02] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:46:21] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:46:40] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:46:59] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:47:18] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:47:37] Sending keep-alive headers... Socket count: 7
[05-12-2017 13:47:56] Sending keep-alive headers... Socket count: 7
^C

HTTP Server Log

user@host:~$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?1061 HTTP/1.1" 200 -
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?524 HTTP/1.1" 200 -
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?1171 HTTP/1.1" 200 -
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?984 HTTP/1.1" 200 -
----------------------------------------
Exception happened during processing of request from ('192.168.126.1', 61831)
Traceback (most recent call last):
  File "/usr/lib/python2.7/SocketServer.py", line 290, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 318, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 331, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python2.7/SocketServer.py", line 654, in __init__
    self.finish()
  File "/usr/lib/python2.7/SocketServer.py", line 713, in finish
    self.wfile.close()
  File "/usr/lib/python2.7/socket.py", line 283, in close
    self.flush()
  File "/usr/lib/python2.7/socket.py", line 307, in flush
    self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?634 HTTP/1.1" 200 -
----------------------------------------
Exception happened during processing of request from ('192.168.126.1', 61832)
Traceback (most recent call last):
  File "/usr/lib/python2.7/SocketServer.py", line 290, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 318, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 331, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python2.7/SocketServer.py", line 654, in __init__
    self.finish()
  File "/usr/lib/python2.7/SocketServer.py", line 713, in finish
    self.wfile.close()
  File "/usr/lib/python2.7/socket.py", line 283, in close
    self.flush()
  File "/usr/lib/python2.7/socket.py", line 307, in flush
    self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?296 HTTP/1.1" 200 -
192.168.126.1 - - [05/Dec/2017 13:48:01] "GET /?640 HTTP/1.1" 200 -
----------------------------------------
Exception happened during processing of request from ('192.168.126.1', 61834)
Traceback (most recent call last):
  File "/usr/lib/python2.7/SocketServer.py", line 290, in _handle_request_noblock
    self.process_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 318, in process_request
    self.finish_request(request, client_address)
  File "/usr/lib/python2.7/SocketServer.py", line 331, in finish_request
    self.RequestHandlerClass(request, client_address, self)
  File "/usr/lib/python2.7/SocketServer.py", line 654, in __init__
    self.finish()
  File "/usr/lib/python2.7/SocketServer.py", line 713, in finish
    self.wfile.close()
  File "/usr/lib/python2.7/socket.py", line 283, in close
    self.flush()
  File "/usr/lib/python2.7/socket.py", line 307, in flush
    self._sock.sendall(view[write_offset:write_offset+buffer_size])
error: [Errno 32] Broken pipe
----------------------------------------

Snort Alerts

12/05-13:45:05.348446  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61831 -> 192.168.126.128:80
12/05-13:45:24.351347  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:45:43.355735  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:46:02.358688  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:46:21.363346  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:46:40.365834  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:46:59.369221  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:47:18.372293  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:47:37.374814  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80
12/05-13:47:56.378355  [**] [1:1:1] SlowLoris.py DoS attempt [**] [Priority: 0] {TCP} 192.168.126.1:61828 -> 192.168.126.128:80

Hope this helps!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.