Schutz für Webseiten mit HAProxy - Tiny WAF

Warum Tiny- WAF? Die hier beschriebenen Schutzmechanismen beziehen sich auf die Abwehr von DDOS Angriffen und der einfachen Filterung von Requests. Zu einer vollständigen Web Application Firewall, die z.B. auch zum virtual patching verwendet werden kann, fehlen Funktionen, wie z.B. der Scan auf bekannte Verwundbarkeiten (SQL Injection usw.). Realisiert werden könnte dies z.B. durch die Nutzung von mod_security, einem OpenSource WAF Modul.  Durch die immer größer werdende Nutzung des Internets und die stetig steigende Verbreitung von teils mit Sicherheitslöchern belasteten Geräten, wie z.B. im IoT, gehören Angriffe auf Websites zur Tagesordnung. Auch "Abhanden gekommene" Toolsammlungen staatlicher Einrichtungen erleichtern Angriffe auf andere.
Die folgenden Konfigurationsschnipsel sind nicht als das Wunderheilmittel zu verstehen- vielmehr sollen sie einen kleinen Einblick in das geben, was der hochperformante OpenSource Loadbalancer HAProxy in der Lage ist zu leisten.

Block bad requests

Auch im Internet muss man sich an bestimmte Spielregeln halten. Um zumindest einige Regeln zu erzwingen können in der HAProxy Konfiguration ACLs verwendet werden.

Beispiele für Überprüfungen:

hdr_cnt : Anzahl der Einträge im Header mit dem angegebenen Namen

hdr_val : Wert des spezifizierten Eintrags im Header

hdr_len : Länge des genannten Header Felds

acl HDR_DENY hdr_cnt(host) gt 1
acl HDR_DENY hdr_cnt(content-length) gt 1
acl HDR_DENY hdr_val(content-length) lt 0
acl HDR_DENY hdr_cnt(proxy-authorization) gt 0
http-request tarpit if HDR_DENY
Selbstverständlich können auch aufgerufene URLs, Dateiendungen oder der Zugriff auf lokale Verzeichnisse überprüft werden:
acl URI_DENY url_reg -i .*(\.|%2e)
acl URI_DENY path_end -i .exe .pif .dll
acl URI_DENY path_beg /ONE /TWO
acl URI_DENY path_dir -i typo3 Windows
http-request tarpit if URI_DENY

DDOS

DDoS ist ein Angriff mehrerer Systeme, möglicherweise auch Botnets, auf ein System um dieses durch die schiere Anzahl von Anfragen lahm zu legen. Jedes System, auch ein noch so großer Verbund aus Servern, kann eine begrenzte Leistung zur Verfügung stellen. Wird dieser Verbund massiv durch hunderte oder gar tausende Systeme mit Anfragen bombardiert, so muss auch der größte Verbund kapitulieren und kann seine Dienste nicht weiter anbieten. Das Ziel, der DoS, Denial of Service, ist geglückt.

Mit entsprechenden Schutzmechanismen ist man diesen Angriffen zum Glück nicht hilflos ausgeliefert.Die Anfrage sehen für den Server im allgemeinen aus wie Anfragen herkömmlicher Clients.

 

HAProxy protokolliert die anfragenden Client IPs in einer globalen Tabelle, einer stick-table, mit, von wo sie nach einer definierbaren Zeit selbst wieder gelöscht werden.
Diese Tabelle wird wie folgt definiert:

 

 

 

stick-table type ip size 200k expire 2m store conn_cur,conn_rate(4s),http_req_rate(10s),http_err_rate(10s)
tcp-request content track-sc0 src
Die protokollierten Werte sind hierbei:

conn_cur: Anzahl der gleichzeitigen Verbindungen einer Quell IP

conn_rate(4s):     Durchschnittliche Anzahl von Anfragen über 4 Sekunden

http_req_rate(10s): Anzahl von Anfragen durch eine IP über einen Zeitraum von 10 Sekunden

http_err_rate(10s): Anzahl von Fehlern, die durch eine IP erzeugt werden, über einen Zeitraum von 10 Sekunden

 

Die erzeugte Tabelle kann, wie oben definiert, maximal 200.000 Einträge des Typs IP enthalten, die nach 2 Minuten verfallen. Sollte die Tabelle voll sein, löscht HAProxy die Werte nach einer LRU Logik.

Erst die Zeile "tcp-request content track-sc0 src" sendet Daten an die definierte Tabelle und macht sie damit nutzbar.

 

Beispiele zur sinnvollen Nutzung im Rahmen eines rudimentären DDOS- Schutzes sind:

 

Bei mehr als 40 IP Verbindungen in 4 Sekunden wird die Verbindung verweigert:

tcp-request connection reject if { sc0_conn_rate ge 40 }

Wenn der Client bereits mehr als 20 IP Verbindungen offen hat werden neue Verbindungen verweigert:

tcp-request connection reject if { sc0_conn_cur ge 20 }

Client blockieren wenn er bereits 10 Verbindungen offen hat:

http-request tarpit if { src_conn_cur ge 10 }

Client blockieren wenn er mehr als 30 Verbindungen in den letzten 3 Sekunden geöffnet hat:

http-request tarpit if { src_conn_rate ge 30 }

Client blockieren wenn er mehr als 20 HTTP Fehler in den letzten 10 Sekunden erzeugt hat:

http-request tarpit if { sc0_http_err_rate() gt 20 }

Client blockieren wenn er in den letzten 10 Sekunden mehr als 100 HTTP Anfragen erzeugt hat:

http-request tarpit if { sc0_http_req_rate() gt 100 }

 

TARPIT ist hier die definierte Methode wie mit einem Überschreiten der definierten Limits umgegangen werden soll. Hierbei werden Anfragen vom jeweiligen Client für eine Dauer blockiert, die durch den Parameter "timeout tarpit", definiert wird.

Slowloris

Während einer DDOS Attacke kommt vermehrt auch ein Verfahren zum Einsatz, das Slowloris genannt wird.

Hierbei sendet der Client seine Anfragen nur sehr sehr langsam, teilweise mit weniger als einem Zeichen pro Sekunde, und blockiert damit Ressourcen auf dem Server.

HAProxy kann dies verhindern, in dem die maximale Zeit, die eine Anfrage dauern darf, begrenzt wird:

 

timeout http-request 5s

All together now

Fügt man dies nun zusammen, könnten die relevanten Teile einer Konfiguration mit DDOS Schutz und der rudimentären Prüfung auf fehlerhafte Header wie folgt aussehen:
default
    ...
    timeout http-request 5s
    timeout tarpit 60s
    ...
    
frontend webserver
    ...
    # DDOS related stuff
    stick-table type ip size 200k expire 2m store conn_cur,conn_rate(4s),http_req_rate(10s),http_err_rate(10s)
    tcp-request content track-sc0 src
    tcp-request connection reject if { sc0_conn_rate ge 40 }
    tcp-request connection reject if { sc0_conn_cur ge 20 }
    http-request tarpit if { src_conn_cur ge 10 }
    http-request tarpit if { src_conn_rate ge 30 }
    http-request tarpit if { sc0_http_err_rate() gt 20 }
    http-request tarpit if { sc0_http_req_rate() gt 100 }
    ...
    # Bad requests
    acl HDR_DENY hdr_cnt(host) gt 1
    acl HDR_DENY hdr_cnt(content-length) gt 1
    acl HDR_DENY hdr_val(content-length) lt 0
    acl HDR_DENY hdr_cnt(proxy-authorization) gt 0
    http-request tarpit if HDR_DENY
    # Bad URLs
    acl URI_DENY url_reg -i .*(\.|%2e)
    acl URI_DENY path_end -i .exe .pif .dll
    acl URI_DENY path_beg /ONE /TWO
    acl URI_DENY path_dir -i typo3 Windows
    http-request tarpit if URI_DENY
    ...

Das Hostsystem

Auf keinen Fall darf die Hostseite, also das Betriebssystem des Loadbalancers selbst, bei den Maßnahmen übersehen werden!
Einige der (D)DOS Methoden sind nur dort zu verhindern.

State- Exhaustion

Bei der State-Exhaustion Attacke, einer Variante einer (D)DOS Attacke, hält der Angreifer die Verbindung offen, was dazu führt, dass auf der Firewall oder dem Loadbalancer, die pro Verbindung einen Eintrag vorhalten, schnell ein Limit erreicht wird. Dieses Limit beträgt z.B. bei einem unmodifizierten Ubuntu 14.04 LTS 65535 Einträge, variiert aber je nach Distribution. Den aktuellen Wert kann man unter Linux mit

cat /proc/sys/net/netfilter/nf_conntrack_max

herausfinden.

Ein Überschreiten dieses Limits, also eine volle Statetable quittiert Linux mit

nf_conntrack: table full, dropping packet

und lehnt weitere Verbindungen ab. Der Server ist nicht mehr erreichbar- der Denial Of Service Angriff war erfolgreich.

Das Anpassen dieses Wertes auf z.B. 1 Million Einträge (1024000) erreicht man unter Ubuntu 14.04 durch

 

sysctl -w net.netfilter.nf_conntrack_max=1024000

 

Damit diese Anpassung auch nach einem Neustart des Systems wieder angewendet wird, ist ein Eintrag in /etc/sysctl.conf erforderlich. 

Wichtig bei der Änderung ist, dass man nicht aus den Augen verliert, dass jede Verbindung, bzw. jeder Eintrag in der Statetable ca. 300 Byte Platz an RAM belegt. Als Faustformel kann man von 512 MB RAM pro 1,5 Millionen Einträgen ausgehen.


Synflooding

Einer Synflood Attacke liegt der eigentliche Ablauf eines Vebindungsaufbaus, der Dreiwege- Handshake, zu Grunde.

Der Dreiwege- Handshake läuft normal wie folgt ab:

  1. Der Client sendet an den Server ein SYN- Paket (synchronize)

  2. Der Server quittiert den Verbindungswunsch mit einem SYN ACK (synchronize acknowledge)

  3. Der Client sendet an den Server ein ACK- Paket (acknowledge). Die Verbindung ist hergestellt und wird für die weitere Datenübertragung genutzt.

Eines der Probleme ist hierbei der Schritt 2. Der Server speichert die Adresse und den Zustand der Verbindung in einer Tabelle (Backlogtable, TIME_WAIT Tabelle). Sendet der Client, ein böswilliger Angreifer, nun kein ACK Paket, so bleibt der Eintrag eine begrenzte Dauer (ca. 1 Minute) in der Tabelle vorhanden. Dies kann bei vielen derartigen Paketen dazu führen, dass diese Tabelle voll wird und der Server neue Verbindungen ablehnt.

Eine Methode hier Abhilfe zu schaffen ist der Einsatz sogenannter Syncookies. Hierbei generiert der Server im Schritt 2 anhand eines Algorithmus eine bestimmte Sequenznummer, sendet diese an den Client, legt aber keinen Eintrag in der Tabelle an. Die generierte Nummer erkennt der Server im ACK Paket des Clients (Schritt 3) wieder, und kann somit von einer legitimen Verbindung ausgehen. Während hierbei zwar das Problem des Speicherplatzes für halboffene Verbindungen umgangen wird, erzeugt diese Methode eine erhöhte CPU Last, was vom Angreifer wiederum ausgenutzt werden kann. Hier hilft es nur noch die Anzahl der möglichen SYN Verbindungen durch ein Ratelimit zu begrenzen. Dies kann z.B. durch einen Eintrag in iptables realisiert werden.

Aber: Dieser Eintrag beeinflusst nicht nur den Angreifer, sondern auch legitime Verbindungen und somit die Service- Availability.