Im Herbst 2021 hat unsere Website ein Hack erwischt. Darüber haben wir damals kurz berichtet. Nun wollen wir euch ausführliche Informationen zu dem Vorfall geben: Wie konnte der Angriff passieren und wie haben wir darauf reagiert? Wir haben aus dem Vorfall gelernt: Auch diese Erkenntnisse wollen wir mit euch teilen. Dieses Update blieb lange aus, da wir viel über die Außenkommunikation diskutiert haben. Aber wir haben euch ein Update versprochen und wollen dieses Versprechen halten.
Was war eigentlich passiert? Als Redaktionssystem verwenden wir bei netzpolitik.org WordPress und nutzen zahlreiche Plugins, um die Blogging-Software für unsere Zwecke anzupassen. Die Plugins stammen teilweise von Drittanbieter:innen – denen können wir die Schuld aber nicht in die Schuhe schieben. Zwei Ursachen haben den Hack ermöglicht: Einerseits haben wir Datei-Berechtigungen unseres Webserver nicht korrekt gesetzt (dazu später mehr).
Andererseits hatte ein von uns verwendetes WordPress-Plugin eine Sicherheitslücke im Admin-Bereich, durch die Schadsoftware auf unseren Server gelangt ist. Diese veränderte Code auf unserem Webserver und ermöglichte den Vollzugriff auf den Webserver durch eine Hintertür. Dieser Zugang hätte für verschiedene Angriffe verwendet werden können. Soweit wir wissen, ist aber ausschließlich folgendes passiert: Die Angreifer:in benutzte die Hintertür, um den Besucher:innen unserer Website ein Banner anzuzeigen, das zum angeblichen „Installieren eines Browser-Updates“ aufforderte.
Tatsächlich hätten die Leser:innen sich beim Klick auf dieses Phishing-Banner Schadsoftware heruntergeladen. Das Phishing-Banner war ausschließlich für Windows-User:innen zu sehen und sie mussten das Herunterladen und danach die Installation aktiv auslösen. Wir wissen von keinem Fall, in dem sich Leser:innen infiziert haben. Unserer Bewertung nach handelte es sich um eine allgemeine Malware-Kampagne, die sich nicht gezielt gegen uns oder unsere Leser:innenschaft gerichtet hat.
Vom Hack betroffen war allein der Server, auf dem wir WordPress betrieben haben. Alle anderen Dienste, die netzpolitik.org betreibt, sind isoliert auf separaten Servern und waren von dem Hack daher nicht betroffen. Für den Versand von Newslettern benutzten wir allerdings auch das betroffene WordPress-System. Deshalb hätte die Angreifer:in theoretisch auch darauf zugreifen können.
Das Newsletter-System enthält die E-Mailadressen der Abonnent:innen und damit sensible personenbezogene Daten. Wir konnten keine Anzeichen finden, dass ein:e Angreifer:in auf diese Daten zugegriffen hat. Dennoch haben wir alle Abonnent:innen per E-Mail informiert.
Es ist nicht nur ärgerlich, dass es zu diesem Vorfall gekommen ist, sondern uns auch sehr peinlich. Die Analyse des Hacks zeigt Schwachstellen in unseren Systemen auf und wir haben zahlreiche Konsequenzen gezogen. Wir bitten bei allen Abonnent:innen und Leser:innen um Entschuldigung, deren E-Mail-Adresse wir nicht ausreichend geschützt haben, so wie es sich für personenbezogene Daten gehören würde.
Chronologischer Ablauf des Hacks
Konfigurationsfehler (10.10.)
Beim Upgrade unseres WordPress-Core von Version 5.8.0 auf 5.8.1 verändern wir Datei-Zugriffsrechte, die das Update erfordert. Danach setzen wir die Rechte nicht wieder auf die vorgesehenen restriktiven Einstellungen zurück. Es sind dadurch mehr Dateien und Verzeichnisse schreibbar als für den normalen Betrieb der Website nötig. So wird dem Angriff der Boden geebnet.
Angreifer:in fügt Remote-Shell ein (29.10., 12:48 Uhr)
Das betreffende Plugin zeigt im Admin-Bereich regulär einen News-Bereich an, in dem die Autor:innen des Plugins Inhalte von einem externen Server nachladen. Das Plugin ermöglicht es, Beiträge automatisch in sozialen Netzwerken zu teilen. Obwohl wir dieses Social-Media-Plugin nicht mehr benutzen, haben wir es noch nicht deaktiviert. Wenn eine angemeldete User:in mit Administrationsrechten diese Unterseite im WordPress-Admin-Bereich aufruft, lädt das Plugin also automatisch Daten eines fremden Servers nach – und in diesem Fall führt der Browser darin enthaltenen JavaScript-Code auch aus. Diese Funktion – Code von einem anderem Server nachladen und lokal ausführen – ermöglicht Cross-Site-Scripting (XSS) und stellt sich später als der Infektionsvektor heraus.
Wir kennen den betreffenden XSS-Code nicht, können aber das Funktionsprinzip rekonstruieren: Da die Rechte der WordPress-User:in ausreichen, den WordPress-internen Theme-Editor zu verwenden, kann das JavaScript über diese Editor-Schnittstelle die Datei „search.php“ verändern. Die Datei ist Teil unseres WordPress-Themes und wegen des Konfigurationsfehlers im Verzeichnis „wp-content/themes/“ fälschlicherweise schreibbar.
Im Access-Log unseres Proxy-Servers kann der Zugriff auf den Theme-Editor nachvollzogen werden. Die genaue Seite und IP-Adresse ist unkenntlich gemacht:
Oct 29 12:48:01 proxy haproxy[24579]: 185.220.70.0 - - [29/Oct/2021:10:48:01 +0000] "GET /wp-admin/theme-editor.php?file=search.php HTTP/1.1" 403 1510 "" "" 56248 971 "NP_FRONTEND~" "VARNISH_BACK" "vpsieprod" 0 0 0 190 190 ---- 190 190 0 1 0 0 0 "" "" "https://netzpolitik.org/wp-admin/admin.php?page=XYZ" "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Saf" ""
Oct 29 12:48:02 proxy haproxy[24579]: 185.220.70.0 - - [29/Oct/2021:10:48:01 +0000] "POST /wp-admin/admin-ajax.php?_fs_blog_admin=true HTTP/1.1" 200 1439 "" "" 56248 732 "NP_FRONTEND~" "VARNISH_BACK" "vpsieprod" 0 0 0 470 470 ---- 194 194 0 1 0 0 0 "" "" "https://netzpolitik.org/wp-admin/admin.php?page=XYZ" "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Saf" "146"
Oct 29 12:48:16 proxy haproxy[24579]: 185.220.70.0 - - [29/Oct/2021:10:48:16 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 662 "" "" 56248 758 "NP_FRONTEND~" "VARNISH_BACK" "vpsieprod" 0 0 0 100 100 ---- 185 185 0 1 0 0 0 "" "" "https://netzpolitik.org/wp-admin/admin.php?page=XYZ-curation" "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Saf" "146"
Die Änderung der Datei besteht aus einer Zeile hinzugefügtem PHP-Code, der eine Art primitive Remote-Shell auf die Such-Seite von netzpolitik.org einbaut. (PHP-Code der Remote-Shell in Datei „search.php“ auf VirusTotal)
Angreifer:in testet Remote-Shell (5.11., 20:15 Uhr)
Eine Woche später macht die Angreifer:in das erste Mal davon Gebrauch, wie der Apache-Access-Log zeigt:
./np.de.access.log.2:176.9.0.0 - - [05/Nov/2021:19:15:56 +0100] "GET /wp-content/themes/liebefeld/search.php?a=echo(\"xxx-\".getcwd());&x=5 HTTP/1.1" 200 370 "-" "-"
Wahrscheinlich testet die Angreifer:in damit die Funktionsfähigkeit der Hintertür. Der ausgeführte Befehl „getcwd()“ gibt den Inhalt des Wurzel-Verzeichnisses des Webservers (Webroot) aus.
Angreifer:in nutzt Remote-Shell für Code Injection (6.11., 15:42 Uhr)
Über Remote Code Execution ändert die Angreifer:in die Datei „wp-include/general-template.php“. Diese ist ebenfalls von dem Konfigurationsfehler betroffen und damit für den Webserver schreibbar. Wir können später im Log nachvollziehen, wie die Angreifer:in die Remote-Shell dafür benutzt:
./np.de.access.log.1:176.9.0.0 - - [06/Nov/2021:14:42:02 +0100] "POST /wp-content/themes/liebefeld/search.php?x=5&a=copy(\"php\".\"://input\",\"z.tmp\"); HTTP/1.1" 200 243 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
./np.de.access.log.1:176.9.0.0 - - [06/Nov/2021:14:42:02 +0100] "POST /wp-content/themes/liebefeld/search.php?x=5&a=require(\"z.tmp\"); HTTP/1.1" 200 288 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
./np.de.access.log.1:176.9.0.0 - - [06/Nov/2021:14:42:03 +0100] "POST /wp-content/themes/liebefeld/search.php?x=5&a=unlink(\"z.tmp\"); HTTP/1.1" 200 243 "-" "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
Die Angreifer:in fügt also verschleierten (base64-obfuscated) JavaScript-Schadcode in eine PHP-Funktion in „general-template.php“ ein. Die Funktion liefert beim Laden jeder netzpolitik.org-Seite diesen Code aus und Browser, die kein JavaScript blockieren, führen ihn als „normalen“ Teil der Website aus. (VirusTotal: gesamte manipulierte Datei „general-template.php“ mit Schadcode in Funtion wp_head(), extrahierter Schadcode aus Funktion „wp_head()“ in „general-template.php“)
Ein Browser, der JavaScript ausführt, lädt beim Aufbau einer netzpolitik.org-Seite ein weiteres externes JavaScript nach, wenn es sich bei der Besucher:in um eine Windows-User:in handelt. In diesem Fall lädt der Browser ein Banner, das zum Installieren eines angeblichen Browserupdates auffordert. Weder das Skript, das dieses Banner den Leser:innen unterschiebt, liegt uns vor (wir können nur die URL nennen, von der es nachgeladen wurde: Link zu JavaScript auf VirusTotal, Link zum nachgeladenen JavaScript auf VirusTotal), noch der Payload des vermeintlichen Browserupdates, bei dem es sich mit Sicherheit um Schadsoftware handelt.
Leser:in meldet Banner (6.11., 19:17 Uhr)
Ein:e Leser:in macht uns per E-Mail auf das Schadsoftware-Download-Banner aufmerksam und unser IT-Team reagiert.
Reaktion des IT-Teams (6.11., 19:33 Uhr)
Unsere Seite zeigt das Banner für knapp vier Stunden für die Windows-User:innen unter unseren Leser:innen an. Dann konfigurieren wir den Webserver so, dass er eine statische Wartungsseite ausliefert, statt dynamische Inhalte der WordPress-Installation auszuführen. Dadurch ist netzpolitik.org erst einmal nicht mehr verfügbar und wir verhindern, dass Leser:innen sich infizieren können.
Da wir unser WordPress-Theme mit einer Versionskontrolle verwalten, finden wir die manipulierten Dateien schnell. Die Sofortmaßnahme besteht darin, das infizierte Webroot-Verzeichnis auf einen anderen Server zu verschieben, um es später zu analysieren. Anschließend kopieren wir das Webroot-Verzeichnis von einem WordPress-Entwicklungsserver auf den Server des Live-Systems. Der Entwicklungsserver ist ein Klon unserer Website, auf der wir Änderungen testen, bevor sie live gehen.
Eine Analyse des neuen Webroot-Verzeichnis gibt keinerlei Hinweise darauf, dass es infiziert sein könnte. Der Entwicklungsserver ist außerdem nicht öffentlich erreichbar. Daher sind wir uns sicher, schnell mit einer sauberen Website wieder online gehen zu können. Wir überprüfen Dateiberechtigungen im Webroot-Verzeichnis und passen die Einstellungen an. Damit haben wir den ursprünglichen Konfigurationsfehler vom vorhergegangenen WordPress-Update behoben. Auf ähnliche Weise stellen wir die Datenbank aus einem tagesaktuellen Backup wieder her, nachdem Scans der Datenbank keine Anzeichen auf Manipulation ergeben.
Wir deaktivieren alle Plugins, die für den Betrieb der Seite nicht essenziell sind, da wir das Einfallstor zu dem Zeitpunkt noch nicht identifiziert haben. So verringern wir die Angriffsfläche für eine Wiederholung des Hacks. Das Social-Media-Plugin, das die Schwachstelle enthalten hatte, ist auch unter den deaktivierten Plugins. Nach etwa zwei Stunden kann netzpolitik.org wieder online gehen: auf dem selben Server, mit einem gescannten Datenbank-Backup und einer sauberen Kopie des Webroot-Verzeichnisses.
Analyse der Logs (7.11., 1:00 Uhr)
Zu diesem Zeitpunkt sind wir noch nicht vollkommen sicher, ob die Infektion wirklich auf das Webroot-Verzeichnis von WordPress beschränkt geblieben ist. Nach der ersten Wiederherstellung entscheiden wir uns deshalb, den Server komplett zu ersetzen und nur WordPress-Daten zu migrieren.
Meldungen an Datenschutzbehörde und BSI (7.11.)
Wir melden den Vorfall umgehend bei der Berliner Datenschutzbehörde, da ein Datenzugriff auf die E-Mailadressen der Newsletterabonennt:innen theoretisch möglich gewesen wäre. Außerdem informieren wir die Meldestelle des Bundesamtes für Sicherheit in der Informationstechnik (BSI) und schicken im Laufe der Untersuchungen weitere Informationen.
Kontakt mit Plugin-Autor:innen (8.11.)
Wir erhalten von Jörg Schieb weitere Hinweise zur Infektion über das betreffende Social-Media-Plugin (Vielen Dank!). Die Erkenntnisse fügen sich wie Puzzleteile erst über den mehrtägigen Analyseprozess hinweg zusammen und wir melden den Fall den Autor:innen des Plugins.
Plugin Autor:innen entfernen News-Bereich serverseitig (9.11.)
Die Plugin-Autor:innen deaktivieren daraufhin umgehend den News-Bereich in dem Plugin, um zu verhindern, dass weitere User:innen, die das Plugin auch installiert und entsprechende Dateiberechtigungen gesetzt hatten, sich infizieren können. Damit schließen sie das Einfallstor für alle Nutzer:innen des Plugins. Das Plugin haben wir dennoch deinstalliert und werden es nicht mehr nutzen.
Wir informieren die Newsletter-Abonnent:innen (9.11., 17:11 Uhr)
Per E-Mail unterrichten wir die Newsletter-Abonnent:innen über den Vorfall, denn der Zugriff auf ihre E-Mail-Adressen wäre theoretisch möglich gewesen.
Welche Konsequenzen haben wir gezogen?
Seitdem arbeiten wir am Hardening unserer Systeme. Zum einen haben wir ein stets laufendes Sicherheitsmonitoring, das Schreibzugriffe meldet. Zum anderen haben wir das Verteilen von User-Rechten für WordPress-User:innen radikal eingeschränkt. Die Anzahl der WordPress-Plugins von Dritten haben wir stark reduziert. Wir werden unser Newsletter-System von WordPress trennen, um die personenbezogenen Daten besser zu schützen.
Wer noch weiter analysieren möchte: Auf Anfrage stellen wir euch den Schadcode zur Verfügung.
Meinen hohen Respekt an euch für eure Offenheit und Ehrlichkeit!
Ich weiß von ähnlich gelagerten Fällen, die solches vertuscht haben.
Als weitere Maßnahme würde sich hier der Einsatz eines Content Security Policy Headers anbieten. Das sollte zumindest das Nachladen von externen Scripten verhindern.
Wir hatten CSP in betrieb zur zeit des hacks, das plugin womit wir es betrieben haben, jedoch, nach dem hack entfernt. Wir arbeiten an erweiterten CORS und CSP regeln derweil!
Danke für die nüchterne Darstellung und die Offenheit um Detail!
Respekt!
So ausfühlich habe ich nicht mal zu aktiven Zeiten hausintern darlegen können.
„Geht ja wieder….“
Was mich erstaunt ist der zeitliche Ablauf, incl. Meldeketten.
Das Risikomanagement wurde nicht erst eingeführt ;)
Beste Grüße
an so einer transparenten Informationspolitik können sich einige Großkonzerne mal ein Beispiel nehmen
Vielen Dank für diese ausführliche Analyse! Hoffen wir, dass andere WordPress Website-Betreiber:innen daraus lernen.
Hier wird wieder einmal deutlich, wie sehr das Addon NoScript zur Sicherheit beitragen kann. Mit NoScript ist JS aus „fremden“ Quellen default gesperrt. Das bedeutet: Wer NoScript verwendet und Erlaubnisse nur mit Umsicht vergibt, bekam nicht einmal das gefälschte Werbebanner zu sehen!
Ein Tipp für den Webmaster und alle Gleichgesinnten: Das Plugin „WordFence“ ermöglicht schon in der kostenlosen Version eine Härtung von WP, die über Bordmittel weit hinaus geht: 2FA, Brute-Force-Schutz, Firewall u.v.m. Meine Empfehlung!
Respekt dafür, wie korrekt ihr in voller Linie damit umgegangen seid, intern in extern. Wirklich vorbildlich!
„Seitdem arbeiten wir am Hardening unserer Systeme. Zum einen haben wir ein stets laufendes Sicherheitsmonitoring, das Schreibzugriffe meldet.“
Damit ich vielleicht auch etwas von eurem Breach lerne: Ist die Frage gestattet, wie ihr das genau implementiert habt? :)
Ansonsten: ein sehr vorbildliches Post Mortem. Vielen Dank für eure transparente Art damit umzugehen, sowas ist nicht selbstverständlich!
Das Log weist mutmaßlich auf die Verwendung von NordVPN (?) und einem „random User-Agent“-Plugin hin aber wisst ihr sicher alleine was das über X:in aussagt. Shit happens. ;)
Vielen Dank für den Beitrag. Sehr spannend zu sehen wie so ein Zugang / Zugriff / Hacken zustande kommt und (grade für jemand der auch WordPress nutzt) beunruhigend daß das Leuten mit professioneller IT Abteilung und dem nötigen Gespür für Datenschutz trotzdem noch passiert.
Gratulation zu schnellen Response und zu den gezogenen Konsequenzen. Chapeau zum Umgang mit dem ganzen Vorgang.
Mich würde noch interessieren warum nach dem Update von WordPress die Dateien Schreiber waren. Ist das ein Problem im automatischen WordPress Update oder liegt das daran dass ihr das Update „von Hand“ macht und dabei vergessen wurde die frisch geschriebenen Dateien auf „nur Lesen“ zu setzen.
Thx 👍
Soll heißen
…warum nach dem Update … die Dateien schreibbar waren…