Cybersicherheit · Authentifizierung · OAuth · Mindestprivilegien
Die Hintertür sichern: Wie ich den Admin-Login meiner Website gesichert habe
Wer eine Website und deren Admin-Panel kontrolliert, kontrolliert auch, was jeder Besucher sieht und ausführt. Hier ist, wie der Admin für diese Seite funktioniert, ohne dass ein Server gehackt werden muss, warum der Schlüssel nur eine Tür öffnet und welche Lücke in der Lieferkette ich noch schließen muss.
Die Vordertür einer Website ist der Teil, den Besucher sehen. Das Admin-Panel ist die Hintertür, und Angreifer wissen das. Wer das Admin-Panel kontrolliert, kontrolliert, was jeder Besucher liest, und im schlimmsten Fall, welcher Code in den Browsern der Besucher ausgeführt wird. Eine entstellte Startseite ist peinlich. Eine Startseite, die still und leise ein schädliches Skript an jeden Besucher ausliefert, ist viel schlimmer.
Als ich also einen Editor für diese Website hinzufügte, damit ich Beiträge von meinem Telefon aus veröffentlichen kann, ohne einen Code-Editor öffnen zu müssen, behandelte ich dessen Login als den sensibelsten Teil des gesamten Systems.

Entscheidung 1: Kein Server, den man hacken kann
Die meisten Content-Management-Systeme laufen auf einem Server mit einer Datenbank von Benutzern und Passwörtern. Dieser Server wird zu einem Ziel: Ein veralteter Plugin, ein schwaches Passwort oder eine exponierte Datenbank können einem Angreifer die gesamte Website in die Hände spielen.
Diese Seite hat nichts dergleichen. Es handelt sich um eine statische Seite, die aus einem Git-Repository auf GitHub neu erstellt wird, sobald sich etwas ändert. Das Admin-Panel (Sveltia CMS) ist nur eine JavaScript-Anwendung, die in meinem Browser läuft und direkt mit der GitHub-API kommuniziert. Das Speichern eines Beitrags bedeutet, einen Commit durchzuführen.
Das verlagert die Sicherheitsfrage. Es gibt keinen Admin-Server, den man patchen muss, und keine Passwort-Datenbank, die ausgeliefert werden kann. Die eigentliche Frage lautet: Wer kann auf das Repository schreiben? Alles andere folgt aus der Antwort.
Entscheidung 2: Ein Schlüssel, der nur eine Tür öffnet
Um auf das Repository zu schreiben, benötigt das CMS ein Token von GitHub. Der einfache Weg, um eines zu erhalten, ist eine klassische OAuth-App mit dem repo Umfang. Aber dieses Token könnte jedes Repository lesen und schreiben, auf das mein Konto zugreifen kann, sowohl öffentlich als auch privat. Wenn es ausgeliefert wird, würde der Schaden weit über diese Website hinausgehen.
Stattdessen erfolgt die Anmeldung über eine GitHub-App, die:
- auf dieses eine Repository installiert ist und
- mit einer Berechtigung ausgestattet ist: Inhalte (lesen und schreiben). Sie kann keine Issues, Einstellungen, Workflows, andere Repositorys oder mein Konto berühren.
Dies ist das Prinzip der Mindestprivilegien: Jeder Schlüssel erhält die kleinstmögliche Macht, die dennoch den Job erledigt, damit der Schaden begrenzt ist, wenn (nicht wenn) etwas schiefgeht.
Entscheidung 3: Den Übergabe schützen
Der OAuth-Anmeldefluss läuft durch einen kleinen Cloudflare-Worker (den Open-Source-sveltia-cms-auth, der unverändert in mein Repository eingebunden ist, an einem festen Commit, damit ich genau weiß, welcher Code ausgeführt wird). Er hält das Client-Geheimnis der GitHub-App als verschlüsseltes Worker-Geheimnis, niemals im Code oder im Repository. Drei seiner Prüfungen sind erwähnenswert, da jede einen echten Angriff blockiert:
- Domain-Zulassungsliste. Der Worker startet die Anmeldung nur für
festusboamah.com. Jemand, der meine Admin-Seite auf seine eigene Domain kopiert, kann meinen Login-Helfer nicht verwenden. - CSRF-Schutz mit
state. Bevor ich zu GitHub weitergeleitet werde, generiert der Worker einen zufälligen Wert, speichert ihn in einemHttpOnly,Secure-Cookie und übermittelt ihn an GitHub als OAuth-state-Parameter. Wenn GitHub mich zurücksendet, müssen die beiden Werte übereinstimmen. Dies verhindert, dass ein Angreifer meinen Browser dazu bringt, eine Anmeldung abzuschließen, die er gestartet hat. - Überprüfte Lieferung. Der Token wird mit
postMessagean das CMS-Fenster übergeben. Der Worker gibt ihn nur frei, wenn die von dem Browser gemeldete Herkunft des anfragenden Fensters meine Domain ist. Diese Herkunft wird vom Browser und nicht von der Seite festgelegt, sodass sie nicht gefälscht werden kann.
Entscheidung 4: Jede Änderung zuordnen
Da jeder Speichervorgang ein Git-Commit zu main ist, hat jede Änderung an der Website einen Autor, einen Zeitstempel und eine genaue Differenz. In Bezug auf STRIDE wird damit die Abstreitbarkeit angegangen: Es gibt keine Änderungen auf der Website, ohne dass ein Eintrag vorhanden ist. Es bietet mir auch eine sofortige Wiederherstellung: Wenn jemals etwas Schlechtes veröffentlicht wird, kann ich den Commit zurücksetzen und Cloudflare Pages baut die saubere Version in etwa einer Minute neu.
Die Admin-Seiten erhalten auch ihre eigenen HTTP-Header: Eine separate Content-Security-Policy, X-Robots-Tag: noindex, damit Suchmaschinen sie nicht auflisten, und Cache-Control: no-store, damit nichts Sensibles zwischengespeichert wird.
Wo es immer noch schwach ist
- Das CMS-Skript hat keine Integritätsprüfung. Die Admin-Seite lädt Sveltia CMS vom unpkg-CDN. Ich pinne eine exakte Version, die vor Überraschungs-Upgrades schützt, aber es gibt keinen Subresource-Integrity-Hash. Wenn das CDN jemals kompromittiert wird und eine modifizierte Datei bereitstellt, würde mein Browser sie mit einem gültigen GitHub-Token ausführen. Dies ist ein klassisches Software-Lieferketten-Risiko. Die Lösung besteht entweder in einem
integrity-Hash im Skript-Tag oder in der Bereitstellung der Datei von meiner eigenen Domain. - Die Admin-CSP ist lockerer als die der Website. Um das CMS zu laden, erlaubt die Admin-Richtlinie Skripte von unpkg und inline-Skripte. Das bedeutet, dass, wenn jemals ein Injection-Bug in der Admin existiert, die Richtlinie ein Skript aus jedem Paket auf unpkg nicht stoppen würde. Das Selbsthosting des CMS würde es mir auch ermöglichen, dies zu verschärfen.
- Alles ruht auf einem GitHub-Konto. Die App beschränkt, was ein Token tun kann, aber wer mein GitHub-Konto kontrolliert, kontrolliert die Website. Das macht eine starke Zwei-Faktor-Authentifizierung auf diesem Konto (idealerweise ein Passkey oder ein Sicherheitsschlüssel anstelle von SMS) zur wichtigsten Kontrolle in diesem gesamten Design, obwohl sie nicht im Code enthalten ist.
Die Lektion
Die nützlichste Sicherheitsentscheidung hier war nicht eine clevere Prüfung. Es war die Entfernung des Servers, dann die Verengung des einen verbleibenden Schlüssels auf das engste Mögliche. Bevor Verteidigungen hinzugefügt werden, sollte immer gefragt werden: Was kann ich entfernen, damit es weniger zu verteidigen gibt?
Haben Sie etwas entdeckt, was ich übersehen habe? Sagen Sie es mir.
