So funktioniert es
Wie ein synchrones Upload-Gate funktioniert
Der einzige Moment, in dem Sie eine Webshell aufhalten statt finden können, ist der Moment ihrer Ankunft. Auf einem LiteSpeed-Host liegt dieser Moment innerhalb von PHP — was zugleich die Gelegenheit und der Haken ist.
Das Fenster, das Sie schließen wollen
Ein Angreifer lädt um 09:15:02 über ein verwundbares Plugin eine Datei hoch und ruft sie um 09:15:04 ab. Zwei Sekunden. Jeder Erkennungsmechanismus mit einer längeren Latenz hat in diesem Fall einen Vorfall dokumentiert statt ihn verhindert.
Asynchrone Erkennung ist wirklich wertvoll — die meisten Kompromittierungen werden nicht innerhalb von Sekunden ausgenutzt, und die Shell in fünf Sekunden statt in achtzehn Stunden zu finden ändert alles an der Bereinigung. Wenn Sie den Upload aber aufhalten wollen, muss die Prüfung stattfinden, während die Anfrage noch unterwegs ist.
Drei Orte für die Prüfung, und warum zwei davon hier nicht taugen
In der Anwendung. Ein PHP-Sicherheitsplugin kann einen Upload prüfen, bevor die Anwendung ihn speichert, und für eine einzelne Website funktioniert das gut. Auf einem Hostingserver nicht: Es muss in jeder Website installiert und aktuell gehalten werden, Kunden schalten es ab, und es teilt das Schicksal des Prozesses, in dem es läuft — ein Angreifer, der in diesem Prozess bereits PHP ausführen kann, kann es abschalten. Das ist eine architektonische Feststellung, keine Kritik an einem bestimmten Plugin.
Im Webserver, mit ModSecurity. Der Operator @inspectFile übergibt eine hochgeladene
temporäre Datei an ein externes Programm, das ein Urteil zurückgibt, und auf Apache ist das ein
ausgetretener Pfad. Auf OpenLiteSpeed existiert er, aber mit zwei dokumentierten Vorbehalten: Er
gilt nur für dynamische Anfragen, und die Ausgabesemantik unterscheidet sich von der auf Apache —
das Skript muss 1 als erstes Zeichen ausgeben, um zu blockieren. Ein Produktversprechen auf
einen Mechanismus mit so viel Variation zwischen den Server-Editionen zu bauen, ist eine
Einladung zu Supportlast. Shelltrap behandelt einen ModSecurity-Adapter als Option für LiteSpeed
Enterprise nach einem Integrationstest je Build, nicht als allgemeine Antwort.
In PHP selbst, bevor die Anwendung läuft. PHP hat eine Konfigurationsdirektive,
auto_prepend_file, die vor jedem Skript eine Datei lädt. Setzen Sie sie in der globalen
lsphp-Konfiguration, und Sie haben einen Haken, der vor jedem Kundencode läuft, bei jeder
PHP-Anfrage, auf OpenLiteSpeed ebenso wie auf LiteSpeed Enterprise, ganz ohne ModSecurity.
Das ist der Mechanismus, den Shelltrap verwendet, und es sei klar gesagt, dass das keine neue Idee ist: Es ist derselbe Haken, den andere PHP-Sicherheitsprodukte auf Host-Ebene aus denselben Gründen verwenden.
Was tatsächlich passiert
Die vorangestellte Datei ist bewusst dünn. Ihre erste Handlung ist die Prüfung, ob $_FILES
nicht leer ist; bei der überwiegenden Mehrheit der Anfragen ist es leer, und der Adapter tut
überhaupt nichts Messbares. Das ist wichtig: Dieser Code läuft bei jeder PHP-Anfrage auf dem
Server.
Wenn es doch einen Upload gibt, öffnet der Adapter einen lokalen Unix-Socket und sendet eine
Zeile JSON, die das Document Root, den Servernamen und die temporären Dateien beschreibt. Der
Broker öffnet jede temporäre Datei mit O_RDONLY|O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC, verlangt eine
reguläre Datei mit passendem Eigentümer, erzwingt die konfigurierte Maximalgröße und übergibt
Deskriptoren an einen Scan-Worker. Eine Anfrage trägt höchstens 128 Dateien und höchstens 1 MiB
je Protokollzeile.
Das Urteil kommt als eine Zeile zurück: allow oder deny, mit einem Grund und einer Dauer.
malicious bedeutet sofortiges Ablehnen. suspicious bedeutet nur dann Ablehnen, wenn die
Domain-Policy heuristics.action=quarantine sagt — andernfalls blockiert ein heuristischer Wert
keinen Upload. unscanned und degraded bleiben im Datensatz sichtbar, werden aber nie von sich
aus zu einem deny, denn eine Ressourcengrenze ist kein Beleg für Bösartigkeit.
Bei einer Ablehnung gibt der Adapter HTTP 403 zurück und löscht die temporäre Datei. Bei einer Erlaubnis läuft die Anfrage genau so zur Anwendung weiter, wie sie es ohnehin getan hätte.
Das Budget, und warum es kurz ist
Das Gate hält eine HTTP-Anfrage offen, während es wartet, deshalb ist sein Zeitbudget eine für
Nutzer sichtbare Latenz. upload.timeout_ms liegt standardmäßig bei 2000 ms und ist auf 60000 ms
begrenzt.
Zwei Sekunden klingen willkürlich, bis man betrachtet, was damit abgewogen wird. Länger bedeutet
tiefere Archivprüfung und weniger Dateien, die als unscanned herausfallen, um den Preis eines
sichtbar langsameren Upload-Formulars und weiterer gleichzeitiger Worker, die Deskriptoren
halten. Kürzer bedeutet eine flottere Website und mehr Arbeit, die an den asynchronen Watcher
hinter dem Gate weitergereicht wird. Die Policy gilt je Domain, damit eine Fotogalerie und ein
Dokumentenportal sich nicht auf eine Antwort einigen müssen.
Das Zeitlimit wird vom Broker erzwungen, selbst wenn eine Scanner-Implementierung den ihr übergebenen Kontext ignoriert, und die Zahl der Worker, die Deskriptoren halten, ist begrenzt. Ein Gate, das man zum Auflaufen bringen kann, ist ein Denial-of-Service-Vektor gegen die Website, die es schützt.
Fail-open, und die Debatte darüber
Die interessante Frage ist nicht, was das Gate tut, wenn es etwas findet. Sie lautet, was das Gate tut, wenn es nicht antworten kann — der Broker startet neu, der Socket fehlt, der Scan lief in ein Zeitlimit.
- fail-open: Der Upload läuft weiter. Die Website funktioniert weiter; eine Datei kann ungeprüft auf die Platte gelangen — wo der Echtzeit-Watcher sie Momente später fängt.
- fail-closed: Der Upload wird mit HTTP 503 abgelehnt. Nichts Ungeprüftes kommt herein; das Kontaktformular oder die Mediathek des Kunden ist kaputt, bis der Scanner zurück ist.
Shelltrap verwendet standardmäßig fail-open und stellt fail-closed je Domain zur Verfügung. Diese Voreinstellung ist eine wohlüberlegte Position, und man darf ihr mit gutem Recht widersprechen.
Die Begründung: Auf einem Shared-Hosting-Server ist ein Scannerausfall, der die Upload-Formulare sämtlicher Kunden mit sich reißt, ein vom Sicherheitsprodukt verursachter Ausfall. Er erzeugt Druck, das Sicherheitsprodukt zu entfernen, was ein schlechteres Sicherheitsergebnis ist als die Datei, die durchgekommen ist — denn die durchgekommene Datei wird ohnehin Sekunden später vom Watcher gefangen. Das Gate ist eine Latenzverbesserung gegenüber dem Watcher, nicht die einzige Verteidigungslinie, und eine Komponente der Tiefenverteidigung sollte kein Single Point of Failure für das sein, was sie verteidigt.
Für eine Domain, bei der ein ungeprüfter Upload wirklich schlimmer ist als ein kaputtes Formular
— etwa ein Dokumentenportal in einem regulierten Umfeld —, ist upload.on_error = closed nur
einen Policy-Schlüssel entfernt, und der Gate-Fehler bleibt in beiden Fällen vollständig
diagnostizierbar.
Die Einschränkung, die wir auf die Packung schreiben
auto_prepend_file kann durch eine .user.ini-Datei innerhalb einer Website überschrieben
werden. Ein Angreifer, der bereits in ein Document Root schreiben kann, kann sie daher woanders
hinlenken und das Gate aus seinem eigenen Weg räumen.
Das ist eine echte Einschränkung, und wir nennen sie lieber, als dass sie entdeckt wird. Daraus folgen drei Dinge:
- Genau dieses Muster ist ein Heuristik-Treffer. Eine
.user.ini, die aus einer Website herausauto_prepend_filesetzt, ist eines der stärksten Konfigurationssignale, die es gibt, und wird auch als solches bewertet. Sie können es im Signal Explorer sehen. - Der Watcher bleibt hinter dem Gate als Auffangnetz. Das Gate zu entfernen entfernt nicht die Erkennung; es entfernt den Vorsprung von zwei Sekunden.
- Diese
.user.inizu schreiben ist selbst ein Ereignis. Es ist ein Schreibvorgang in einem überwachten Document Root, und die Ereignismaske umfasst Attributänderungen gerade deshalb, damit reine Konfigurationsangriffe sichtbar sind.
Eine Verteidigung, die von einem Angreifer abgeschaltet werden kann, der bereits Schreibzugriff hat, ist nicht nutzlos. Sie ist eine Schicht, und sie sollte als eine solche beschrieben werden.
Was das Gate nicht abdeckt
Das Gate deckt PHP-Web-Uploads ab. Dateien kommen auch per FTP, SFTP, WebDAV, über den
Dateimanager des Panels, git, wget und eine Shell an.
FTP bekommt einen eigenen Weg: pure-uploadscript ruft einen Einreiher auf, der sofort
zurückkehrt, sodass der Scan direkt nach dem Upload stattfindet statt währenddessen. Dieser
Charakter als Prüfung nach dem Upload wird im Produkt benannt statt beschönigt, denn „wir scannen
FTP-Uploads“ und „wir scannen FTP-Uploads, bevor sie landen“ sind sehr verschiedene Aussagen.
Alles Übrige wird asynchron vom Echtzeit-Watcher gefangen, typischerweise innerhalb von Sekunden.