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
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:
| 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.
| 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.
| 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:
- Nachkontrolle nach 24 und 72 Stunden. Dateien erneut nach Änderungsdatum durchsehen. Kommt etwas zurück, ist ein Wiederherstellungsmechanismus übrig.
- Prüfung bei Google beantragen. In der Search Console unter Sicherheitsprobleme, sonst bleibt die Warnung im Suchergebnis stehen.
- Eintrag bei Blacklists prüfen. Ist die Domain gelistet, blockieren Browser und Mailserver sie weiter, obwohl sie sauber ist.
- 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.
- 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 ArbeitsweiseWeiterlesen
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.
Anfrage ist da.
Ich melde mich innerhalb eines Werktags. Schau sicherheitshalber auch in den Spam-Ordner.