Zum Inhalt springen

WordPress-Schadcode finden und entfernen: die sechs Verstecke

Schadcode in WordPress steckt selten an einer Stelle. Typisch sind zwei bis drei Orte gleichzeitig, darunter mindestens einer, der nach der Bereinigung alles wiederherstellt. Deshalb ist die wichtigere Frage nicht, wie man ihn entfernt, sondern wie man sicher ist, alles gefunden zu haben.

Von Dennis Theis 10 Min. Lesezeit

Nahaufnahme eines Laptops, auf dem Quelltext geöffnet ist
Sechs Orte. Wird einer übersehen, ist die Seite nach Stunden wieder befallen.

Woran du Schadcode erkennst

Schadcode arbeitet leise, weil er von Reichweite lebt. Er zeigt sich deshalb meist indirekt, und oft merkt es jemand anderes zuerst. Die verlässlichen Anzeichen:

  • Google Search Console meldet unter Sicherheitsprobleme einen Fund
  • fremde Weiterleitungen, oft nur auf Mobilgeräten oder nur bei Besuchern, die über Google kommen
  • unbekannte Seiten in der Google-Suche, etwa mit japanischen Zeichen oder Pharma-Begriffen
  • Mails aus dem Nichts: der Hoster meldet Spamversand oder sperrt den Versand
  • Dateien mit frischem Änderungsdatum, obwohl niemand etwas geändert hat
  • Serverlast ohne Anlass, weil im Hintergrund etwas läuft

Ein einzelnes Anzeichen reicht für den Verdacht. Und ein negativer Plugin-Scan reicht nicht für die Entwarnung: Sicherheits-Plugins finden Signaturen bekannter Schadsoftware. Ein handgeschriebener Einzeiler in der functions.php hat keine Signatur.

Erst sichern, dann suchen

Bevor du etwas löschst: mach eine vollständige Kopie von Dateien und Datenbank, und zwar ausdrücklich mit dem Schadcode. Das klingt verkehrt, ist aber wichtig. Du brauchst sie aus drei Gründen: um vergleichen zu können, wenn du dich verlaufen hast, um nachzusehen, was der Code eigentlich getan hat, und für die Dokumentation, falls personenbezogene Daten betroffen waren und eine Meldung fällig wird.

wp db export befall-$(date +%F).sql
tar czf befall-$(date +%F).tar.gz wp-content wp-config.php .htaccess

Zweiter Schritt, bevor du suchst: die Zugänge stilllegen. Passwörter aller Administratoren erneuern und die Security Salts in wp-config.php austauschen. Das wirft alle laufenden Sitzungen aus, auch die des Angreifers. Sonst räumst du auf, während jemand zusieht.

Die sechs Verstecke

In der Praxis liegt Schadcode fast immer an einem dieser sechs Orte. Die Reihenfolge ist die ihrer Häufigkeit:

Die sechs Orte im Überblick
Ort Was dort liegt Woran du es erkennst
.htaccess Weiterleitungen RewriteRule ohne BEGIN-Markierung, Bedingungen nur für mobile Geräte
wp-content/uploads/ hochgeladene Hintertür PHP-Dateien zwischen den Jahresordnern
themes/…/functions.php eingeschleuster Aufruf eine Zeile am Dateianfang oder Dateiende, oft base64-kodiert
wp-content/mu-plugins/ dauerhaft aktiver Code lädt automatisch, steht in keiner Plugin-Liste im Dashboard
Tabelle wp_options Skript in der Datenbank Optionen mit <script>, die zu keinem Plugin passen
WP-Cron Wiederherstellung geplante Aufgabe, die zu keinem installierten Plugin gehört

1. .htaccess im Stammverzeichnis

Der häufigste Ort für Weiterleitungen. Normal sind Blöcke zwischen # BEGIN WordPress und # END WordPress, dazu markierte Blöcke von Plugins wie WP Rocket oder Wordfence. Verdächtig ist alles ohne solche Markierung, besonders RewriteRule auf fremde Domains und Bedingungen, die nur mobile Geräte oder nur Besucher mit Google-Referrer treffen.

2. wp-content/uploads

Dort gehören Bilder hin, kein ausführbarer Code. Eine .php-Datei zwischen den Jahresordnern ist ein Befund, oft mit unauffälligem Namen wie wp-cache.php oder settings.php.

3. functions.php des aktiven Themes

Meist eine einzige Zeile, ganz am Anfang oder ganz am Ende, häufig kodiert. Wer ein Child Theme nutzt, muss beide Dateien prüfen.

4. wp-content/mu-plugins

Must-Use-Plugins laden automatisch und erscheinen in der Plugin-Liste des Dashboards nicht. Für einen Angreifer ist das der bequemste Ort überhaupt: aktiv, aber unsichtbar. Manche Hoster legen hier selbst eine Datei ab, das ist üblich, alles andere gehört geprüft.

5. Die Tabelle wp_options

Schadcode muss nicht in einer Datei stehen. In wp_options landen Optionen, die auf jeder Seite ausgegeben werden, etwa Skripte in Kopf- oder Fußbereich. Ein Eintrag mit <script> darin, der zu keinem Plugin passt, ist ein Befund.

6. WP-Cron

Der Grund, warum ein Befall zurückkommt. Eine geplante Aufgabe schreibt die entfernten Dateien in festem Takt neu. Wer die Dateien bereinigt und den Cron-Eintrag stehen lässt, fängt am nächsten Morgen von vorn an.

Schadcode finden: die Suchbefehle

Es gibt zwei Wege, und beide führen zum Ziel. Ohne Terminal arbeitest du im Dateimanager deines Hosters und in phpMyAdmin, beides erreichst du über das Kundenmenü bei All-Inkl, IONOS, Strato oder wem auch immer die Seite liegt. Mit SSH-Zugang geht dasselbe in ein paar Befehlen. Wenn du beim Wort Terminal aussteigst, lies die linke Spalte und überspring die Befehle darunter.

Dieselbe Suche, zwei Wege
Wonach du suchst Im Dateimanager oder phpMyAdmin Mit SSH
veränderte Core-Dateien WordPress in derselben Version herunterladen und Dateigrößen von wp-admin und wp-includes vergleichen wp core verify-checksums
PHP in Upload-Ordnern wp-content/uploads öffnen und nach Dateityp sortieren find wp-content/uploads -name "*.php"
frisch veränderte Dateien im Dateimanager nach Änderungsdatum sortieren, neueste zuerst find . -name "*.php" -mtime -7 -ls
versteckter Code verdächtige Datei öffnen und nach eval oder base64_decode suchen, Strg+F grep -rEl "eval\(|base64_decode" wp-content
Must-Use-Plugins nachsehen, ob es den Ordner wp-content/mu-plugins überhaupt gibt ls -la wp-content/mu-plugins/
Skripte in der Datenbank in phpMyAdmin die Tabelle wp_options öffnen und nach <script suchen wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%<script%'"

Der Rest dieses Abschnitts sind die Befehle im Einzelnen, jeweils im WordPress-Stammverzeichnis auszuführen.

Core gegen die offiziellen Prüfsummen abgleichen:

wp core verify-checksums

PHP-Dateien in Upload-Verzeichnissen:

find wp-content/uploads -name "*.php" -o -name "*.phtml" -o -name "*.php7"

Dateien, die in den letzten sieben Tagen verändert wurden:

find . -type f -name "*.php" -mtime -7 -ls

Die typischen Muster verschleierten Codes:

grep -rEl "eval\(|base64_decode|gzinflate|str_rot13|assert\(|preg_replace\(.*/e" \
  wp-content --include="*.php"

Must-Use-Plugins und geplante Aufgaben:

ls -la wp-content/mu-plugins/
wp cron event list

Datenbank auf eingebettete Skripte:

wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%<script%'"

Eines lässt sich ohne Terminal nicht gleichwertig ersetzen: die Suche nach Mustern über alle Dateien. Im Dateimanager bleibt dir, die nach Datum auffälligen Dateien einzeln zu öffnen. Das ist machbar, wenn der Befall frisch ist, und mühsam, wenn er Monate liegt. Spätestens dann ist es kein Selbsthilfefall mehr.

Was harmlos aussieht und keiner ist

Zwei Fehler kosten bei der Suche am meisten Zeit. Der erste: alles für Schadcode halten, was kodiert ist. base64_decode steht in legitimen Plugins, etwa beim Umgang mit Bilddaten oder Lizenzschlüsseln. Entscheidend ist der Kontext, nicht die Funktion. Verdächtig wird es, wenn kodierte Zeichenketten direkt ausgeführt werden, wenn die Datei nicht zum umgebenden Plugin passt oder wenn der Code in einer einzigen sehr langen Zeile steht.

Der zweite Fehler ist der teurere: nur die Symptome behandeln. Die auffällige Datei ist gelöscht, die Weiterleitung ist weg, die Seite sieht gut aus. Die Backdoor, über die die Datei hereinkam, ist aber noch da. Genau das ist der Unterschied zwischen einer Seite, die sauber ist, und einer, die morgen wieder befallen ist. Der Japanese SEO Spam ist das bekannteste Beispiel dafür: der sichtbare Teil sitzt in der Datenbank, der Wiederherstellungsmechanismus ganz woanders.

Entfernen: drei Wege

Welcher Weg der richtige ist, hängt davon ab, was du hast.

Bereinigungswege im Vergleich
Weg Voraussetzung Risiko
Backup zurückspielen ein Backup von vor dem Befall, Zeitpunkt bekannt Inhalte und Bestellungen seit dem Backup sind weg
Core und Plugins ersetzen Liste aller Erweiterungen, keine Änderungen an fremdem Code individuelle Anpassungen gehen verloren
gezielt bereinigen alle Fundstellen bekannt eine übersehene Backdoor genügt

Am belastbarsten ist der zweite Weg, und er ist weniger aufwendig, als er klingt: Core und alle Plugins und Themes aus den offiziellen Quellen frisch installieren, sodass nur wp-content/uploads, die Datenbank und die eigenen Anpassungen übrig bleiben, und diese drei dann gezielt prüfen. Damit sind die Verstecke 1 bis 4 in einem Zug erledigt.

wp core download --force
wp plugin list --field=name | xargs -n1 wp plugin install --force
wp theme install $(wp theme list --status=active --field=name) --force

Der Zeitpunkt des Befalls ist die andere wichtige Größe. Er steht im Zugriffsprotokoll des Servers, und ohne ihn ist ein Backup ein Ratespiel: ein Archiv von gestern hilft nicht, wenn der Code seit drei Wochen liegt.

Nach der Bereinigung

Bereinigt ist nicht dasselbe wie abgeschlossen. Fünf Dinge gehören noch dazu:

  1. Nachkontrolle nach 24 und 72 Stunden. Dateien erneut nach Änderungsdatum durchsehen. Kommt etwas zurück, ist ein Wiederherstellungsmechanismus übrig.
  2. Prüfung bei Google beantragen. In der Search Console unter Sicherheitsprobleme, sonst bleibt die Warnung im Suchergebnis stehen.
  3. Eintrag bei Blacklists prüfen. Ist die Domain gelistet, blockieren Browser und Mailserver sie weiter, obwohl sie sauber ist.
  4. Meldepflicht bewerten. Waren personenbezogene Daten zugänglich, läuft die 72-Stunden-Frist nach Art. 33 DSGVO ab Kenntnis, nicht ab Bereinigung. Details und die Haftungsfrage stehen im Artikel zur Meldepflicht.
  5. Das Einfallstor schließen. Ohne diesen Schritt ist die Bereinigung eine Pause. Wie du die offenen Stellen findest, steht im Artikel zur Sicherheitsprüfung, laufend überwacht wird es in der WordPress-Wartung.

Wann du es abgeben solltest

Selbst bereinigen ist realistisch, wenn du SSH-Zugang hast, den Zeitpunkt des Befalls kennst und die Seite keine personenbezogenen Daten in größerem Umfang verarbeitet. Vier Dinge sprechen dagegen:

  • kein Dateizugriff, nur das Dashboard
  • der Befall kommt nach der Bereinigung zurück
  • ein Shop mit laufenden Bestellungen, bei dem ein Backup echten Umsatz kostet
  • Kundendaten betroffen, also eine Meldung nötig, die belegbar sein muss

Ist noch offen, ob überhaupt ein Befall vorliegt, ordnet die WordPress-Soforthilfe das Symptom zuerst ein. Ich übernehme solche Fälle zum Festpreis von 590 EUR netto, mit Start am selben Werktag, inklusive Absicherung, Blacklist-Entfernung und drei Wochen Nachsorge. Was dazugehört, steht auf der Seite zur Malware-Bereinigung.

Quellen

Dennis Theis

Freiberuflicher Webentwickler aus Berlin. Schwerpunkt sind Websites, die an die Systeme angebunden sind, in denen die Daten schon liegen.

Mehr über meine Arbeitsweise

Weiterlesen

Schadcode gefunden und keine Lust auf Katz und Maus?

Ich übernehme Wartung, Updates und die Anbindung an deine Systeme. Schreib mir, was ansteht, und du bekommst eine Einschätzung, bevor irgendjemand ein Angebot schreibt.

theis@devslab.de +49 174 7284457 WhatsApp: schnelle Nachricht

Anliegen Soforthilfe