diff --git a/.htaccess b/.htaccess index fa44a65..685daec 100644 --- a/.htaccess +++ b/.htaccess @@ -10,6 +10,24 @@ Options -Indexes RewriteEngine On + # ------------------------------------------------------------------ + # 0. TLS erzwingen - und zwar VOR jeder Route + # ------------------------------------------------------------------ + # Die Stelle ist der Punkt. Alle Routen darunter tragen [L]; stuende die + # Umleitung dahinter, wuerde sie fuer genau die Adressen nie erreicht, + # die es betrifft - login.php, index.php, die gesamte API. + # + # Ergaenzt HSTS, ersetzt es nicht: HSTS wirkt erst, nachdem der Browser + # die Anlage einmal ueber TLS gesehen hat. Die erste Anfrage ist genau + # die, die abgefangen wuerde, und die faengt diese Regel ab. + # + # Die zweite Bedingung gilt TLS-terminierenden Zwischenstellen: Dort ist + # %{HTTPS} leer, obwohl der Besucher verschluesselt spricht. Ohne sie + # liefe die Umleitung im Kreis. + RewriteCond %{HTTPS} !=on + RewriteCond %{HTTP:X-Forwarded-Proto} !=https + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] + # Bereits umgeschriebene Anfragen nicht erneut anfassen. RewriteRule ^public/ - [L] @@ -74,7 +92,11 @@ Options -Indexes # 5. Alles Uebrige sperren: Anwendungscode, Konfiguration, Skripte # ------------------------------------------------------------------ RewriteRule ^(config|src|sql|scripts|cli|var|client-dotnet|client-php|mcp)(/|$) - [F,L] - RewriteRule ^Serverdaten\.txt$ - [F,L] + # Auch die Sicherungskopie. deploy.py schliesst beide aus, die Datei + # sollte also nie hier liegen - aber die zweite Verteidigungslinie muss + # zur ersten passen, sonst deckt sie genau den Fall nicht ab, fuer den es + # sie gibt. + RewriteRule ^Serverdaten\.txt(\..*)?$ - [F,L] # ---------------------------------------------------------------------- @@ -85,12 +107,47 @@ Options -Indexes Header always set X-Frame-Options "SAMEORIGIN" Header always set Referrer-Policy "strict-origin-when-cross-origin" Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()" + + # HSTS: Nach dem ersten Besuch spricht der Browser diese Adresse nur noch + # ueber TLS an - auch dann, wenn jemand einen http-Link unterschiebt. Die + # Umleitung weiter unten schuetzt erst ab der zweiten Anfrage; die erste + # ist genau die, die abgefangen wird. + # + # Ohne preload und ohne includeSubDomains waere es die halbe Wirkung; mit + # includeSubDomains gilt es fuer jede Unteradresse von mhdf.de. Wer hier + # eine Unteradresse ohne TLS betreibt, macht sie damit unerreichbar - das + # ist der Preis und der Grund, warum die Zeile bewusst hier steht und + # nicht in einer Vorlage. + Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" + + # ------------------------------------------------------------------ + # Content-Security-Policy - und was sie hier NICHT leistet + # ------------------------------------------------------------------ + # 'unsafe-inline' steht in script-src, und das ist keine Nachlaessigkeit, + # sondern eine Tatsache ueber den Bestand: index.php enthaelt 93 Inline- + # Handler (onclick, onsubmit, onchange) und mehrere tausend Zeilen + # Inline-Skript. Ohne 'unsafe-inline' funktioniert die Oberflaeche nicht - + # mit ihr schuetzt die Richtlinie nicht gegen eingeschleustes Skript. + # + # Das ehrlich hinzuschreiben ist wichtiger, als es zu verschweigen: Eine + # CSP im Header laedt sonst dazu ein, XSS fuer erledigt zu halten. Was + # sie hier WIRKLICH leistet: + # - kein Skript von fremden Adressen (Exfiltration ueber