WordPress-Webshells im Upload-Ordner finden und blockieren
So finden Sie WordPress-Webshells in wp-content/uploads mit sicheren Befehlen, sperren PHP unter Apache und nginx, prüfen Dateirechte und schließen das Einfallstor.
Auch auf: English Français Español Italiano Polski Português Türkçe
Bei Verdacht auf eine WordPress-Webshell im Upload-Ordner stehen drei getrennte Aufgaben an. Sie müssen die Datei finden und als Beweismittel sichern. Sie müssen verhindern, dass direkte Anfragen PHP in wp-content/uploads ausführen. Und Sie müssen den Weg schließen, über den die Datei auf den Server kam. Jede dieser Aufgaben löst ein anderes Problem, und keine davon ist für sich allein eine vollständige Wiederherstellung.
Im Folgenden finden Sie zu jeder Aufgabe die Befehle und Servereinstellungen und erfahren, was die jeweilige Maßnahme nicht leistet.
Was eine Webshell ist und warum wp-content/uploads so beliebt ist
Eine Webshell ist ein kleines Skript, meist in PHP, das einem Angreifer eine Fernsteuerung für Ihren Server verschafft. Je nach Server kann er damit Befehle ausführen, die wp-config.php samt Datenbankpasswort auslesen, die Datenbank abfragen und weitere Dateien hochladen.
Angreifer bevorzugen wp-content/uploads, weil WordPress dort normalerweise Schreibrechte braucht. Medien, Formularanhänge und Plugin-Exporte landen in diesem Ordner, und seine Dateien werden direkt per URL ausgeliefert. Gelangt eine PHP-Datei dorthin und führt der Server sie aus, kann jeder im Internet sie aufrufen.
Wie Webshells in wp-content/uploads gelangen
Die üblichen Einfallswege sind wenig spektakulär:
- Ein Upload-Formular, das jeden Dateityp annimmt, etwa ein Kontaktformular- oder Bewerbungs-Plugin, das Anhänge im Upload-Ordner ablegt.
- Eine Lücke in einem Plugin oder Theme, die das Hochladen beliebiger Dateien erlaubt (Arbitrary File Upload).
- Fehlerhafte Zugriffskontrolle (Broken Access Control): Eine Funktion, die nur für Administratoren gedacht ist, kann von jedem aufgerufen werden. Der Bericht Patchstack State of WordPress Security in 2026, ein Herstellerbericht, nennt dies die am häufigsten ausgenutzte Kategorie.
- Ein gestohlenes Konto in Kombination mit einer Schwachstelle. Die Standardprüfung der Dateitypen in WordPress hindert einen Redakteur normalerweise daran, eine
.php-Datei hochzuladen. Zum Upload-Weg wird ein gestohlenes Konto erst, wenn diese Rolle zusätzlich eine Upload-Lücke in einem Plugin erreicht oder die Konfiguration der Website ihr die Berechtigungunfiltered_uploadgewährt. Ein Administrator auf einer Einzelinstallation kann in der Regel Plugins installieren und die Datei-Editoren im Dashboard nutzen.DISALLOW_FILE_EDITschaltet diese Editoren ab;DISALLOW_FILE_MODSsperrt zusätzlich die Installation und Aktualisierung von Plugins und Themes über das Dashboard. In einer Multisite haben nur Super-Admins diese Rechte.
Webshells heißen selten shell.php. Achten Sie auf doppelte Endungen wie invoice.pdf.php, auf die alternativen Endungen .phtml und .phar sowie auf Namen, die Cache- oder Vorschaudateien imitieren. PHP-Code in einer .jpg-Datei ist ein Warnsignal, wird aber nicht allein dadurch ausgeführt, dass er dort steht. Er läuft nur, wenn der Server die Datei an PHP übergibt oder ein anderes anfälliges Skript sie einbindet.
Das Risiko konzentriert sich auf Plugins. Patchstack ordnet 91 % der 2025 neu bekannt gewordenen Schwachstellen im WordPress-Ökosystem Plugins zu. Für stark ausgenutzte Schwachstellen nennt der Bericht einen gewichteten Median von 5 Stunden bis zur massenhaften Ausnutzung. Wie Sie bei diesem Tempo mithalten, erklärt unser Leitfaden zu anfälligen Plugins.
WordPress-Webshell aufspüren: PHP-Dateien im Upload-Ordner
Die folgenden Befehle verändern keine Dateien. Führen Sie sie per SSH im WordPress-Stammverzeichnis aus, also in dem Ordner, in dem die wp-config.php liegt. Beginnen Sie mit den naheliegenden Endungen:
find wp-content/uploads -type f \( -iname '*.php' -o -iname '*.phtml' -o -iname '*.phar' \) -ls
invoice.pdf.php wird gefunden, weil der Name auf .php endet. Um Varianten wie .php5 oder image.php.jpg zu erfassen, erweitern Sie das Muster auf -iname '*.ph*' – und rechnen mit mehr Fehltreffern.
Lassen Sie sich anschließend kürzlich geänderte Dateien anzeigen oder Dateien, die neuer sind als ein Upload, den Sie sicher als sauber kennen:
find wp-content/uploads -type f -mtime -7 -ls
find wp-content/uploads -type f -newer wp-content/uploads/2026/09/known-good.jpg -ls
Änderungszeiten lassen sich fälschen, nehmen Sie sie also nur als Hinweis. So finden Sie PHP-Code in Dateien mit Bildnamen:
grep -rli --include='*.jpg' --include='*.jpeg' --include='*.png' --include='*.gif' --include='*.webp' --include='*.ico' '<?php' wp-content/uploads
Beachten Sie: grep liest Dateiinhalte, und das Lesen kann die Zugriffszeit einer Datei aktualisieren. Wenn Sie bereits von einer Kompromittierung ausgehen, bitten Sie Ihren Hoster zuerst um einen Snapshot.
Nicht jeder Treffer ist bösartig. Manche Plugins legen eine index.php als Platzhalter an, die nur einen Kommentar enthält, und einige Cache- oder Formular-Plugins schreiben PHP in ihren eigenen Unterordner. Bevor Sie eine verdächtige Datei öffnen, sichern Sie ihre Metadaten wie im nächsten Abschnitt beschrieben. Untersuchen Sie sie dann mit less oder xxd file | head -40. Öffnen Sie sie niemals im Browser, denn dabei kann sie ausgeführt werden. Lange kodierte Zeichenketten, eval, base64_decode oder assert in Verbindung mit Request-Variablen sind deutliche Warnzeichen.
Durchsuchen Sie außerdem das Access-Log nach Anfragen an PHP-Dateien im Upload-Ordner. Ein POST an eine solche Datei ist selten legitim:
grep -E '"(GET|POST) /wp-content/uploads/[^ ]*\.(php|phtml|phar)' access.log
Treffer gefunden? Erst Beweise sichern, dann löschen
Halten Sie zuerst die Metadaten fest, kopieren Sie dann die Datei und die Logs. Legen Sie alles in einem privaten Verzeichnis ab, das außerhalb jedes vom Server ausgelieferten Document Root liegt. Bei manchen Shared-Hosting-Anbietern ist schon das Home-Verzeichnis per Web erreichbar – prüfen Sie den Document Root daher in Ihrem Kundenmenü oder in der vhost-Konfiguration.
umask 077
mkdir -p ~/ir-evidence && chmod 700 ~/ir-evidence
f="wp-content/uploads/path/to/suspicious.php"
case_id="webshell-$(date -u +%Y%m%dT%H%M%SZ)-$RANDOM"
stat "$f" > ~/ir-evidence/"$case_id"-stat.txt
sha256sum "$f" > ~/ir-evidence/"$case_id"-sha256.txt
tar -czpf ~/ir-evidence/"$case_id"-file.tar.gz "$f"
tar -czpf ~/ir-evidence/"$case_id"-logs.tar.gz /path/to/access.log* /path/to/error.log*
Zeitstempel und Zufallssuffix verringern das Risiko, einen Dateinamen doppelt zu vergeben; prüfen Sie trotzdem auf vorhandene Dateien, bevor Sie die Sicherung wiederholen. Hoster rotieren Logs, kopieren Sie diese also sofort. Laden Sie die Beweismittel danach an einen sicheren Ort außerhalb des Servers herunter und notieren Sie den Pfad sowie die passenden Logzeilen.
Gehen Sie davon aus, dass die Datenbank-Zugangsdaten aus der wp-config.php offengelegt sein könnten. Prüfen Sie, ob personenbezogene Daten eingesehen, offengelegt, verändert, verloren oder unzugänglich gemacht wurden: Eine funktionierende Shell ist ein ernstes Warnsignal, belegt aber für sich allein noch keine Verletzung des Schutzes personenbezogener Daten im Sinne der DSGVO. Nach Art. 33 müssen Sie eine solche Datenpanne unverzüglich und möglichst binnen 72 Stunden, nachdem sie Ihnen bekannt wurde, der zuständigen Aufsichtsbehörde melden – es sei denn, sie führt voraussichtlich nicht zu einem Risiko für die Rechte und Freiheiten natürlicher Personen. Art. 33 Abs. 5 verlangt, dass Sie jede Verletzung dokumentieren, auch solche, die Sie nicht melden. Einzelheiten stehen in unserem 72-Stunden-Leitfaden. Für die vollständige Bereinigung folgen Sie unserem Plan zur Malware-Entfernung.
PHP-Ausführung im Upload-Ordner sperren (Apache und nginx)
Legitime Mediendateien müssen nie als PHP laufen. Unter Apache 2.4 legen Sie dazu wp-content/uploads/.htaccess an:
<FilesMatch "\.(?i:php|phtml|phar)$">
Require all denied
</FilesMatch>
Apache berücksichtigt diese Datei nur, wenn AllowOverride es für das Verzeichnis zulässt; für Require bedeutet das All oder mindestens AuthConfig. Sind Overrides deaktiviert, bitten Sie den Hoster, den Block in einen <Directory>-Abschnitt zu setzen, der auf den absoluten Dateisystempfad des Upload-Ordners genau dieser Website beschränkt ist. Setzen Sie ihn nicht ohne diese Eingrenzung in den VirtualHost, sonst kann er PHP auf der gesamten Website blockieren.
Unter nginx ergänzen Sie den server-Block um diese Zeile:
location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ { deny all; }
nginx ermittelt zuerst die längste passende Präfix-Location. Ist diese mit ^~ versehen, etwa location ^~ /wp-content/uploads/, prüft nginx gar keine Regex-Locations mehr, und Ihre Regel wird ignoriert. Dann gehört die deny-Regel in genau diesen Block. Andernfalls werden Regex-Locations der Reihe nach geprüft, und der erste Treffer gewinnt – die Regel muss also vor Ihrem allgemeinen Block location ~ \.php$ stehen. Liegt WordPress in einem Unterverzeichnis wie /blog/ oder nutzt es eine eigene Upload-URL, passen Sie den Pfad an. Prüfen Sie die tatsächlich wirksame Konfiguration und laden Sie nginx dann neu:
sudo nginx -T | grep -n 'location'
sudo nginx -t && sudo systemctl reload nginx
Bei Managed Hosting bitten Sie den Support, die Regel einzurichten.
Diese Regeln weisen direkte Web-Anfragen auf die genannten Endungen ab, sofern die Serverkonfiguration sie tatsächlich anwendet. Sie decken nicht jede ausführbare Endung ab und verhindern nicht, dass ein anderes PHP-Skript eine hochgeladene Datei einbindet. Sie löschen keine Shells und verhindern keine Uploads. Fragen Sie Ihren Hoster, welche Endungen an PHP übergeben werden, und erweitern Sie die Liste bei Bedarf.
Die Regel mit einer Testdatei prüfen
Verwenden Sie einen neuen, zufälligen Dateinamen. Mit noclobber überschreibt der Befehl keine vorhandene Datei, und er löscht nur eine Datei, die er selbst angelegt hat:
t="wp-content/uploads/exec-test-$(date +%s)-$RANDOM.php"
if ( set -o noclobber; printf '%s\n' '<?php echo "marker-" . (40 + 2);' > "$t" ); then
curl -sS -i "https://example.com/$t"
rm -- "$t"
else
echo "Not created: $t already exists"
fi
Enthält die Antwort marker-42, hat PHP die Datei ausgeführt – diese Zeichenkette steht nämlich nicht in der Datei selbst. Sehen Sie dagegen die rohe <?php-Zeile mit Status 200, lief PHP zwar nicht, aber der Server gibt Quellcode preis. Das ist ein eigenes Problem, das Ihr Hoster lösen muss. Ein Status 200 allein beweist nichts. Ein 403 zählt nur, wenn Ihre Regel ihn erzeugt hat und nicht etwa ein CDN oder eine WAF. Testen Sie direkt gegen den Ursprungsserver mit curl --resolve example.com:443:ORIGIN_IP und suchen Sie im Error-Log des Ursprungsservers nach „access forbidden by rule“ (nginx) oder „client denied by server configuration“ (Apache).
Dateirechte und Eigentümer in WordPress: prüfen statt pauschal zurücksetzen
Was der PHP-Prozess verändern darf, entscheiden Rechte und Eigentümer gemeinsam. Der Eigentümer allein ist nicht maßgeblich, denn auch Gruppen- und „Other“-Bits sowie ACLs können Schreibzugriff gewähren. Führen Sie kein rekursives chmod über die gesamte Website aus. Das kann private Dateien wie Backups für alle lesbar machen und Wartungsskripten das Ausführungsrecht entziehen.
Stellen Sie zuerst fest, wem die Dateien gehören und unter welchem Benutzer PHP läuft:
stat -c '%U:%G %a %n' wp-config.php wp-content wp-content/uploads
ps -eo user,group,comm | grep -E 'php-fpm|php|httpd|apache2' | sort -u
Bei vielen Shared-Hosting-Anbietern läuft PHP unter Ihrem eigenen Kontobenutzer. Normale Nur-Lese-Rechte schützen in diesem Fall wenig: Der Eigentümer einer Datei kann sie in der Regel zurücksetzen und sich den Schreibzugriff wiederholen, sofern keine anderen Dateisystem- oder Hosting-Beschränkungen das verhindern.
Listen Sie als Nächstes auf, was der PHP-Benutzer tatsächlich schreiben darf, und zusätzlich alles, was für jeden beschreibbar ist. Ersetzen Sie PHPUSER durch den ermittelten Benutzer:
sudo -u PHPUSER find . -writable ! -type l -print
find . -perm -o+w ! -type l -ls
Werten Sie die beiden Listen getrennt aus: Für alle beschreibbare Pfade müssen in der Regel korrigiert werden, ob PHP-beschreibbare Pfade in Ordnung sind, hängt dagegen von Ihrem Update-Modell aus. Bei einem schreibgeschützten Deployment sollte PHP normalerweise nur in den Upload-Ordner und in benötigte Laufzeitverzeichnisse schreiben dürfen; Updates über das Dashboard und automatische Updates brauchen zusätzliche Schreibrechte. Korrigieren Sie die geprüften Pfade einzeln und mit symbolischer Schreibweise, zum Beispiel chmod g-w,o-w path/to/file. Das Entfernen von Bits lockert nie einen bereits strengeren Modus. Der WordPress-Hardening-Leitfaden nennt 755 für Verzeichnisse und 644 für Dateien als übliche Obergrenze. Setzen Sie die wp-config.php auf 640 oder 600, je nachdem, wie PHP sie liest, und testen Sie die Website danach. Verwenden Sie niemals 777.
Eine Grenze bleibt: PHP muss in den Upload-Ordner schreiben können. Ein anfälliges Plugin, das unter diesem Benutzer läuft, kann dort also weiterhin Dateien ablegen.
define( 'DISALLOW_FILE_EDIT', true ); in der wp-config.php entfernt die Datei-Editoren aus dem Dashboard. DISALLOW_FILE_MODS geht weiter, schaltet aber auch die automatischen Hintergrund-Updates ab. Setzen Sie es nur ein, wenn Sie auf anderem Weg aktualisieren.
Das Einfallstor schließen und den Upload-Ordner weiter überwachen
Mit dem Löschen der Shell ist die Ursache nicht behoben. Solange das Einfallstor offen bleibt, kann die Website jederzeit erneut infiziert werden.
Gleichen Sie die gesicherte stat-Ausgabe mit dem Access-Log ab. Suchen Sie das Formular oder den Plugin-Endpunkt, der kurz vor dem Auftauchen der Datei einen POST erhalten hat. Aktualisieren Sie dann das Plugin oder konfigurieren Sie es um: Beschränken Sie die erlaubten Dateitypen und speichern Sie Anhänge außerhalb des Web-Roots, wenn das Plugin das unterstützt. Gibt es kein sicheres Update und keine Abhilfe, löschen Sie das Plugin. Deaktivieren ist kein verlässlicher Ersatz, weil die Dateien auf dem Server bleiben und manche davon weiterhin direkt erreichbar sein können. Ändern Sie danach die Admin-Passwörter, die Salts in der wp-config.php und die Datenbank-Zugangsdaten.
Behalten Sie den Ordner anschließend im Blick:
- Lassen Sie den kombinierten
find-Befehl täglich per Cronjob laufen und sich jede Ausgabe per E-Mail schicken. - Richten Sie eine Dateiintegritätsüberwachung ein.
- Führen Sie regelmäßig
wp core verify-checksumsaus.
Bei der Überwachung hilft das Plugin Security Audit & Compliance von secaudit.eu. Es legt eine Referenz für die Dateiintegrität an, meldet Änderungen und verschleierte Inhalte und prüft auf Malware-Signaturen und Kompromittierungsindikatoren. Es erkennt und meldet, entfernt aber nichts. Weitere Warnzeichen finden Sie in 12 Anzeichen, dass Ihre WordPress-Website gehackt wurde.
FAQ
Ist jede PHP-Datei in wp-content/uploads bösartig?
Nein. Platzhalter-Dateien namens index.php und manche Plugin-Ordner sind legitim. Sichern Sie die Metadaten, untersuchen Sie die Datei, ohne sie auszuführen, und klären Sie, zu welchem Plugin der Ordner gehört.
Macht ein Sicherheits-Plugin oder eine WAF die Upload-Regel überflüssig?
Nein. Eine Firewall kann ein neues Angriffsmuster übersehen. Die Serverregel ist eine zusätzliche Schutzschicht, die direkte Anfragen auf die genannten Endungen auch dann abweist, wenn ein Upload durchgerutscht ist.
Reicht es, PHP im Upload-Ordner zu sperren, wenn ich schon eine Shell gefunden habe?
Nein. Sichern Sie die Beweise, bereinigen Sie die Website und schließen Sie das Einfallstor. Die Shell könnte bereits genutzt worden sein, um an anderer Stelle Dateien abzulegen oder Ihre Datenbank auszulesen.
Dieser Artikel ist eine allgemeine Information und keine Rechtsberatung.
Prüfen Sie Ihre eigene Website
Security Audit & Compliance ist das WordPress-Plugin von secaudit.eu: Es sucht nach Malware, verstecktem Spam, veränderten Kerndateien und unsicherem Code und ordnet seine Befunde DSGVO Art. 32, NIS2 Art. 21 und den OWASP Top 10 zu.
Sehen, was das Plugin prüft