So funktioniert es
Wie Dateierkennung in Echtzeit funktioniert
Ein Scanner, der nachts läuft, findet die Shell von gestern. Ein Scanner, der bei jedem Schreibvorgang läuft, muss vier Probleme lösen, denen der nächtliche nie begegnet. Hier sind sie, mit sichtbaren Abwägungen.
Das Problem mit geplanten Scans
Ein Cronjob, der um drei Uhr morgens /home durchläuft, hat eine Tugend: Er ist einfach. Alles
andere an ihm ist ein Kompromiss.
Eine um 09:15 Uhr hochgeladene Webshell hat achtzehn Stunden Zeit, benutzt zu werden, bevor irgendetwas sie bemerkt. In diesen achtzehn Stunden kann sie Mails versenden, eine Phishing-Seite ausliefern, in ein anderes Konto wechseln oder eine zweite Hintertür an einer Stelle installieren, an der der nächste Scan nicht nachsieht. Wenn der Scan meldet, ist die gefundene Datei oft längst nicht mehr die interessante — sie ist die, die der Angreifer absichtlich zurückgelassen hat.
Ein Terabyte Kundendaten zu durchlaufen kostet außerdem echte I/O. Auf einem Shared Host spürt das jeder Kunde auf der Maschine, weshalb so viele geplante Scans am Ende bis zur Bedeutungslosigkeit gedrosselt oder stillschweigend abgeschaltet werden.
Echtzeit-Erkennung ändert die Frage von „was liegt auf dieser Platte“ zu „was hat sich gerade geändert“ — eine sehr viel kleinere und sehr viel nützlichere Frage.
Zwei Kernel-Schnittstellen, und warum die Wahl zählt
Linux bietet zwei Wege, über Dateiänderungen informiert zu werden.
inotify überwacht Verzeichnisse. Es ist fast überall verfügbar und benötigt keine besonderen Rechte, und damit endet die gute Nachricht. Jedes Verzeichnis braucht seinen eigenen Watch, Watches sind eine begrenzte Ressource je Benutzer, und die Rekursion ist Ihr Problem: Ein neues Unterverzeichnis braucht einen neuen Watch, und zwischen seiner Erstellung und Ihrem Watch klafft eine Lücke. Auf einem Host mit hunderten Konten und tiefen WordPress-Bäumen erschöpfen Sie entweder das Watch-Limit oder Sie erhöhen ein globales sysctl, das alles andere auf der Maschine betrifft.
fanotify kann einen ganzen Mount markieren. Eine Markierung deckt jede Datei darunter ab,
einschließlich Verzeichnissen, die noch gar nicht existieren. Das beseitigt das
Rekursionsproblem und das Problem der erschöpften Watches in einem Zug. Der Preis ist
CAP_SYS_ADMIN und — für die Modi, die Elternverzeichnis und Namen melden — ein hinreichend
aktueller Kernel mit exportierbaren File-Handles.
Shelltrap bevorzugt fanotify mit FAN_REPORT_DFID_NAME und markiert je Mount, und fällt ehrlich
zurück, wenn das nicht möglich ist:
Die wichtige Entwurfsentscheidung dabei ist nicht, welche Stufe verwendet wird. Sie besteht darin, dass die Stufe gemeldet wird. Ein Container, der nur verzögerte Erkennung leisten kann, sagt das im Produkt, statt sich als Echtzeit auszugeben.
Was als Ereignis zählt
Das naheliegende Ereignis ist „eine Datei wurde geschrieben“. Wer sich darauf beschränkt, verpasst zwei der häufigsten Persistenztechniken auf Shared Hosting, deshalb ist die Maske breiter:
CLOSE_WRITE— ein Schreibvorgang ist abgeschlossen. Der übliche Moment zum Scannen, weil die Datei vollständig ist.MOVED_TOundMOVED_FROM— ein Umbenennen hinein oder heraus. Angreifer legen eine Datei anderswo ab und verschieben sie an ihren Platz, gerade weil ein naiver Watcher nur auf Schreibvorgänge schaut.CREATEundDELETE— Auftauchen und Verschwinden.ATTRIB— Attributänderungen. Eine Datei, die als harmlose Daten hochgeladen und dann ausführbar gemacht oder mit einem setuid-Bit versehen wird, ist ein Angriff, an dem überhaupt kein Schreibvorgang beteiligt ist.
Es gibt auch eine Subtraktion. Eine Markierung auf Dateisystemebene auf dem Root-Dateisystem liefert jedes Öffnen auf dem Host, einschließlich des Datenverzeichnisses des Datenbankservers und jedes Log-Schreibvorgangs. Diese Pfade werden bewusst als ignoriert markiert. Alles zu überwachen ist nicht sicherer; es ist langsamer, lauter und wird eher von einem entnervten Administrator abgeschaltet.
Die Privilegientrennung
Hier ist die unbequeme Wahrheit über jeden Dateiscanner: Er ist ein Programm, das vom Angreifer gewählte Dateien öffnet und sie an Parser für Archive, Bilder, Dokumente und ausführbare Dateien weiterreicht. Das ist der gefährlichste Code im System. Ein Parser-Fehler in einer Archivbibliothek ist ein vom Angreifer kontrollierter Speichersicherheitsfehler, und wenn dieser Parser als root läuft, dann auch der Angreifer.
Deshalb teilt Shelltrap die Aufgabe auf:
Der Broker hat die Privilegien und keine Parser. Er hält die fanotify-Gruppen, öffnet Dateien
mit openat2 und RESOLVE_NO_SYMLINKS (oder mit einem komponentenweisen openat-Durchlauf mit
O_NOFOLLOW, wo dieser Syscall nicht verfügbar ist) und reicht einen nur lesbaren Deskriptor
weiter. Er enthält keinen YARA-Code, keinen ClamAV-Code und kein Parsing von Archiven,
Office-Dateien oder ELF. Er nimmt vom Panel und von der Oberfläche undurchsichtige Objekt-IDs
entgegen, niemals einen Pfad.
Der Worker hat die Parser und keine Privilegien. Er läuft als eigener Benutzer in einem eigenen Mount- und PID-Namespace, mit seccomp, Landlock, rlimits und cgroup-Limits — und ganz ohne Netzwerk. Er erhält Deskriptoren; er löst niemals selbst einen Pfad auf, sodass ein Symlink-Race in einem Kundenverzeichnis nichts hat, wogegen es antreten könnte.
Diese Trennung ist die folgenreichste Entwurfsentscheidung im Produkt, und es lohnt sich, bei jedem Scanner, den Sie prüfen, danach zu fragen: Was läuft als root, und parst es?
Wenn die Warteschlange überläuft
Echtzeitsysteme haben einen Fehlerfall, den Batch-Systeme nicht kennen: Die Ereignisse treffen schneller ein, als sie verarbeitet werden können. Ein Kunde spielt ein Backup zurück, ein Deploy schreibt zehntausend Dateien, ein Angreifer entpackt absichtlich ein Archiv. Die Warteschlange des Kernels füllt sich, und der Kernel meldet Ihnen, dass er Ereignisse verworfen hat.
Es gibt drei mögliche Antworten, und nur eine davon ist ehrlich.
Sie können es ignorieren und stillschweigend eine Abdeckungslücke haben, von der niemand weiß. Sie können abstürzen und gar keine Abdeckung haben. Oder Sie können es zählen, darauf alarmieren und abgleichen: einen begrenzten Scan über den betroffenen Teilbaum laufen lassen, um zu finden, was übersehen wurde.
Shelltrap tut das Dritte. Derselbe Abgleich läuft nach einem Neustart, einem Absturz, einem Upgrade und immer dann, wenn ein neuer Mount unter einem Kundenverzeichnis auftaucht — alles Situationen, in denen Ereignisse passiert sind, denen niemand zugehört hat. Reader und Scan-Warteschlange sind getrennte Goroutinen mit einem persistenten Job-Journal in SQLite, sodass ein Neustart fortsetzt, statt neu zu beginnen.
Das hat einen Preis, und den sollten Sie vor der Installation kennen: Ein erster Start ohne
Marker für ein sauberes Herunterfahren plant einen vollständigen Durchlauf. Auf einem
ausgelasteten Host ist dieser Rückstand echte Arbeit, das Alter der Warteschlange steigt, während
er abgearbeitet wird, und der Gesundheitszustand bleibt degraded, bis das erledigt ist. Das ist
das richtige Verhalten — der Scanner ist tatsächlich im Rückstand —, aber es ist deutlich
angenehmer, wenn Sie cgroup-Limits vor dem ersten Start setzen statt danach.
Warum „nicht gescannt“ eine eigene Antwort ist
Jeder Scanner hat Grenzen: eine maximale Dateigröße, eine maximale Scandauer, eine maximale Rekursionstiefe für Archive, ein maximales Expansionsverhältnis für komprimierte Daten. Ohne sie legt ein einziges präpariertes Archiv die Maschine lahm.
Die Frage ist, was gemeldet wird, wenn eine Grenze erreicht ist. Eine beunruhigende Zahl von Werkzeugen zählt das Objekt stillschweigend als sauber, weil die Alternative eine hässliche Zahl auf einem Dashboard wäre. Damit wird aus einer Ressourcengrenze eine Erkennungslücke, die genau dort unsichtbar ist, wo man nach ihr suchen würde.
Shelltrap hat aus diesem Grund fünf Urteile statt zwei:
| Urteil | Bedeutung |
|---|---|
clean | von jeder Engine im aktiven Profil gescannt, nichts gefunden |
suspicious | mehrere unabhängige Signale, unterhalb der Handlungsschwelle |
malicious | ein Signatur- oder Hash-Treffer oder ein heuristischer Wert über der Schwelle |
unscanned | eine Grenze wurde erreicht; der Grund wird festgehalten und das Objekt für einen begrenzten Tiefenscan eingereiht |
degraded | der Scanner selbst war beeinträchtigt — clamd weg, ein Worker tot, ein Regelsatz fehlt |
unscanned und degraded werden niemals als clean angezeigt. Das macht manche Dashboards
weniger grün. Es macht sie auch wahr.
Der Cache, und warum ein Regel-Update ihn ungültig macht
Dieselbe unveränderte Datei wiederholt zu scannen ist Verschwendung, deshalb werden Urteile zwischengespeichert. Was in den Cache-Schlüssel eingeht, entscheidet darüber, ob der Cache ein Leistungsgewinn oder ein Sicherheitsloch ist.
Der Schlüssel enthält die Mount-ID, die Inode, mtime und ctime in hoher Auflösung, die Größe, den
Inhalts-Hash — und die Generationen von Regelsatz, Engine und Policy. Genau dieser letzte Teil
sorgt dafür, dass ein Signatur-Update alte clean-Urteile automatisch ungültig macht: Eine
Datei, die unter den Regeln von gestern sauber war, ist unter den heutigen Regeln nicht gescannt
worden.
Pfadabhängige Policy-Urteile liegen in einem eigenen Cache, weil sich der Kontext ändern kann, ohne dass sich der Inhalt überhaupt ändert. Dieselbe Inode kann durch einen Hardlink oder eine Umbenennung an einer neuen Stelle auftauchen, und „PHP in einem Uploads-Verzeichnis“ ist eine Aussage über den Ort, nicht über die Bytes.
Was Echtzeit-Erkennung nicht ist
Sie ist keine Prävention. Eine Datei, die geschrieben wird, ist geschrieben; die Erkennung geschieht nach Abschluss des Schreibvorgangs, in Millisekunden statt in Stunden, aber danach. Den Schreibvorgang selbst auf Kernel-Ebene zu blockieren ist mit fanotify-Berechtigungsereignissen möglich und liegt für eine spätere Version als Prototyp vor, wird heute aber bewusst nicht versprochen — denn ein Scanner, der bei jedem Öffnen einer Datei eine Berechtigungsentscheidung zurückhält, ist ein Scanner, der einen Server zum Stillstand bringen kann, wenn er hängt.
Es gibt einen Weg, auf dem Prävention heute existiert, und es ist derjenige, der für einen Webhoster am meisten zählt: das synchrone Upload-Gate , das einen Web-Upload beurteilt, bevor Ihre Anwendung ihn je zu sehen bekommt.
Weiterführende Lektüre
- So funktioniert das Upload-Gate
- Der Webshell Signal Explorer — worüber der Worker tatsächlich urteilt
- Produktüberblick