Im Artikel Cracking the Lens: Targeting HTTP’s Hidden Attack-Surface wird unter anderem gezeigt, welche Auswirkungen nicht valider Host-Header auf die eigene Infrastruktur haben können. Man kann sich vor derartigen Angriffen relativ leicht schützen. Als erstes muss man entscheiden, ob man pfad- oder hostbasiert Routingentscheidungen trifft. Man sollte pro Loadbalancer/Reverseproxy/Webserver nur ein Unterscheidungsmerkmal nutzen.

Wenn man beides zulässt, dann muss festlegen und auch sicherstellen, dass z.B. immer zuerst der Pfad /.well-known/acme-challenge/ auf den Webserver für die Let’s Encrypt Challenge geroutet wird. Anschließend routet man nur noch anhand von Hostnamen. Wenn man keine Reihenfolge sicher stellt baut man evtl. eine Backdoor ein.

Apache und Nginx

Mit Apache und Nginx lässt sich ein auch ein hostbasiertes Routing bauen. Wenn man die folgenden Punkte beachtet, gibt es auch weniger Überraschungen:

  • Default-vhost in der Datei 000_default einrichten
  • ein vhost pro Datei
  • interne vhosts lauschen nur auf internen Interfaces
  • bei Hostnames bzw. Aliases Wildcards vermeiden und lieber alles Domains aufschreiben

Der Default-vhost gibt entweder immer einen Status 500 zurück oder eine nette Fehlerseite. So erhält man immer einen Fehler. Sonst kommt es vor, dass nicht erwünschte Requests vom ersten vhost bearbeitet werden. Gerade die letzte Regel ist wichtig, sowie man Wildcards verwendet verhalten sich die Webserver anders als man denkt. Es gewinnt in der Regel der First-Match und nicht der Best-Match. Ich habe schon einige kreative Konstruktionen gesehen, die offen wie ein Scheunentor waren.

Fallstrick

Die Host-Header werden in Zusammenhang mit Ports nicht immer richtig ausgewert, d.h. es findet mitunter keine Unterscheidung zwichen expample.com, example.com:80 und example.com:8080 statt.

HAProxy

Ich muss zugeben, dass ich ein HAProxy-Fanboy bin, aber wenn man es mit Routingfasching zu tun hat, dann gibt es kein besseres Tool. Wenn man bei HAProxy nur nach Hostnames routen möchte, dann hat sich die folgende Syntax, mit Map-Files, bewährt:

use_backend bk_%[req.hdr(host),lower,map(/etc/haproxy/host2backends.map,error)]

Die Datei /etc/haproxy/host2backends.map besteht aus Key-Value Paaren. Der Key ist der Hostname und der Value ist der Name des HAProxy-Backends ohne den Prefix bk_

cat /etc/haproxy/host2backends.map
# Hostname     backend name
example.com    example.com
example.de     example.com
foo.org        foo.org
foo.net        foo.net

Die Backends nach dem Beispiel haben die Namen bk_example.com, foo.org und foo.net. Diese Map-Dateien sind auch performanter, als einzelne use_backend-Anweisungen, da beim Start ein Präfixbaum aus der Datei generiert wird. Wenn kein Treffer gefunden wurde, dann wird das Backend bk_error genommen. Falls es kein Default-Backend gibt, dann gibt HAProxy den Status-Code 500 zurück.