shelltrap.com
en de

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

Ablauf des synchronen Upload-GatesEin Besucher lädt eine Datei hoch. Der PHP-Adapter läuft vor dem Anwendungscode, reicht die Temp-Deskriptoren an den Broker weiter und wartet höchstens zwei Sekunden. Der Worker scannt, der Broker antwortet mit allow oder deny. Bei deny endet der Request mit 403 und die Temp-Datei wird gelöscht; bei einem Gate-Fehler entscheidet die Policy on_error.BesucherHTTP-Requestlsphpshelltrap-prepend.phpBroker/run/shelltrap/upload.sockWorkerisoliert, ohne NetzPOST multipart/form-data$_FILES nicht leersonst tut der Adapter nichts{v:1, docroot, server_name, files[]}höchstens 128 Dateien · 1 MiB je Zeilenur-lesbarer FD je Temp-DateiO_RDONLY|O_NOFOLLOW · reguläre Datei, passender EigentümerVerdikt je Datei{"decision":"allow|deny","reason","millis"}deny → HTTP 403, Temp-Datei gelöschtallow → Anwendung läuft normal weiterZEITBUDGETupload.timeout_ms — Standard 2000 ms, Maximum 60000 msGATE-FEHLERupload.on_error = open (Standard: Request läuft weiter) · closed (HTTP 503)
Der Adapter tut überhaupt nichts, solange $_FILES leer ist. Ist es das nicht, erhält der Broker Deskriptoren — keine Pfade — und hat ein begrenztes Budget für die Antwort.

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:

  1. Genau dieses Muster ist ein Heuristik-Treffer. Eine .user.ini, die aus einer Website heraus auto_prepend_file setzt, ist eines der stärksten Konfigurationssignale, die es gibt, und wird auch als solches bewertet. Sie können es im Signal Explorer sehen.
  2. Der Watcher bleibt hinter dem Gate als Auffangnetz. Das Gate zu entfernen entfernt nicht die Erkennung; es entfernt den Vorsprung von zwei Sekunden.
  3. Diese .user.ini zu 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.

Weiterführende Lektüre