
CVE-2024-11972 en Hunk Companion <1.9.0 permite a atacantes no autenticados explotar endpoints inseguros de la API REST e instalar plugins vulnerables, lo que conlleva riesgos de RCE, SQLi, XSS y backdoors.
CVE-2024-11972 es una vulnerabilidad crítica en el plugin de WordPress Hunk Companion en versiones anteriores a la 1.9.0. Este fallo permite a atacantes no autenticados explotar endpoints de la API REST con autorización inadecuada, permitiéndoles instalar y activar plugins del repositorio de WordPress.org, incluidos aquellos que están desactualizados o tienen vulnerabilidades conocidas. Explotar esta vulnerabilidad puede conllevar graves riesgos de seguridad, como ejecución remota de código, inyección SQL, XSS o puertas traseras administrativas.
Un ejemplo común de abuso de esta vulnerabilidad es con el plugin WP Query Console. Una vez instalado usando este exploit, el plugin proporciona una interfaz de consola dentro de WordPress donde los usuarios pueden ejecutar consultas SQL contra la base de datos del sitio, permitiendo el robo de datos, la creación de puertas traseras y el compromiso completo de la base de datos.
Bibliotecas Python necesarias -> argparse, requests, urljoin
options:
-h, --help show this help message and exit
-u URL, --url URL Base URL of the WordPress site (default: http://localhost/wordpress/).
-p PLUGIN, --plugin PLUGIN
Plugin name to install (default: classic-editor).
<insert wordpress URL> -p <insert plugin name>El endpoint vulnerable, /wp-json/hc/v1/themehunk-import, fue identificado inicialmente por Daniel Rodriguez durante un análisis de registros de acceso como parte de una investigación en curso.
Con esta información podemos mirar dentro del código fuente del endpoint de importación.
Dentro del archivo /import/core/class-installation.php podemos ver la siguiente línea:
204 | $temp_file = download_url('https://downloads.wordpress.org/plugin/'.$slug.'.zip');
La clase HUNK_COMPANION_SITES_BUILDER_SETUP gestiona la instalación y activación de plugins y temas de WordPress, manejando tanto tipos gratuitos como premium basados en parámetros de entrada.
Verifica dinámicamente si un plugin o tema está instalado o activo, descarga y descomprime los archivos necesarios si faltan, y los activa usando funciones principales de WordPress.
Esta funcionalidad codificada permite al plugin descargar cualquier plugin del repositorio de WordPress, incluso aquellos que han sido eliminados o descontinuados, dando a los atacantes la oportunidad de aprovechar plugins vulnerables para la explotación.
Para profundizar en el problema, podemos mirar dentro de /import/app/app.php,
comparemos las diferentes correcciones introducidas en las versiones 1.8.0 | 1.8.7 | 1.9.0, ya que cada una introdujo otra capa de seguridad:
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => '__return_true',
) );
El permission_callback siempre devuelve true, esto permite que cualquier usuario o actor no autenticado acceda al endpoint mediante una solicitud POST.
Dado que no hay ningún tipo de verificación Nonce ni ningún proceso de autenticación, un atacante podría eludir los permisos necesarios e instalar directamente el plugin deseado.
En la versión 1.8.7, se introdujeron varias mejoras de seguridad para abordar las fallas presentes en versiones anteriores,
pero el endpoint /hc/v1/themehunk-import y la implementación general aún tienen fallas significativas:
public function register_routes() {
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => function () {
// Check if the user is logged in
if ( ! is_user_logged_in() ) {
return new WP_REST_Response( 'Unauthorized: User not logged in', 401 );
}
// Debug: Log the user role and capabilities to see what they have
$current_user = wp_get_current_user();
// error_log( 'Current user: ' . $current_user->user_login );
// error_log( 'User roles: ' . implode( ', ', $current_user->roles ) );
// error_log( 'User capabilities: ' . print_r( $current_user->allcaps, true ) );
// Ensure the user has the 'install_plugins' capability
if ( ! current_user_can( 'install_plugins' ) ) {
return new WP_REST_Response( 'Unauthorized: Insufficient capabilities', 401 );
}
// Get the nonce from the request header
$nonce = $request->get_header('X-WP-Nonce');
// Verify the nonce
if ( ! wp_verify_nonce( $nonce, 'hc_import_nonce' ) ) {
return new WP_REST_Response( 'Unauthorized: Invalid nonce', 401 );
}
return true; // Permission granted
Aunque esta versión incluye comprobaciones para asegurar que el usuario ha iniciado sesión (is_user_logged_in()) y verifica que tenga las capacidades adecuadas (current_user_can('install_plugins')), enviando una respuesta 401 Unauthorized si no las tiene, aún descuida el problema principal que permanece sin resolver.
La lógica de callback de WordPress en lo que respecta a permission_callback se evalúa de la siguiente manera:

Lo que significa que una respuesta correcta sería una de las siguientes: true | false | WP_Error.
El problema surge del hecho de que el WP_REST_Response devuelto no es un booleano ni un WP_Error, dado que WordPress no lo interpreta como una denegación (false) ni como un error (WP_Error(), podría aceptarlo implícitamente y conceder acceso a un usuario no autenticado, esencialmente sin resolver el problema principal.
La versión 1.9.0 resolvió exitosamente el problema, haciendo que la vulnerabilidad ya no sea explotable:
public function register_routes() {
register_rest_route( 'hc/v1', 'themehunk-import', array(
'methods' => 'POST',
'callback' => array( $this, 'tp_install' ),
'permission_callback' => function () {
// Check if the user is logged in
if ( ! is_user_logged_in() ) {
return false;
}
// Debug: Log the user role and capabilities to see what they have
$current_user = wp_get_current_user();
// error_log( 'Current user: ' . $current_user->user_login );
// error_log( 'User roles: ' . implode( ', ', $current_user->roles ) );
// error_log( 'User capabilities: ' . print_r( $current_user->allcaps, true ) );
// Ensure the user has the 'install_plugins' capability
if ( ! current_user_can( 'install_plugins' ) ) {
return false;
}
// Get the nonce from the request header
$nonce = $request->get_header('X-WP-Nonce');
// Verify the nonce
if ( ! wp_verify_nonce( $nonce, 'hc_import_nonce' ) ) {
return false;
}
return true; // Permission granted
En esta versión, podemos ver que se devuelve un valor false adecuado si el usuario no ha iniciado sesión o carece de los permisos correctos, asegurando que el permission_callback deniegue correctamente el acceso no autorizado, haciendo que el exploit ya no sea viable.