
Detector no destructivo para RCE no autenticado en BeyondTrust Remote Support y PRA, encadenando inyección de argumentos y bypass de escape de PostgreSQL para verificar la explotabilidad mediante diferencial basado en tiempo.
Advertencia Ética: Solo para fines de investigación, educativos y de pruebas autorizadas. No lo utilice contra sistemas sin permiso.
BeyondTrust Remote Support (RS) y Privileged Remote Access (PRA) son appliances que intermedian sesiones remotas privilegiadas hacia los sistemas internos de una organización, por lo que con frecuencia están expuestos a internet y ocupan una posición de confianza en la red.
Un atacante no autenticado que pueda alcanzar el endpoint WebSocket del appliance puede ejecutar comandos del sistema operativo como el usuario site encadenando dos fallos: una inyección de argumentos en el script thin-scc-wrapper del appliance (CVE-2024-12356) y una omisión del escape de cadenas de PostgreSQL que convierte un byte UTF-8 no válido en una ruptura temprana del literal SQL (CVE-2025-1094). La ruta vulnerable se alcanza antes de cualquier autenticación.
bt_rs_rce_check.py en este repositorio es un detector no destructivo para esa cadena. Recorre exactamente la ruta de código no autenticada que usa el exploit real, pero se detiene en la prueba de alcanzabilidad: lo único que le pide al objetivo ejecutar es un benigno (una diferencial basada en tiempo) o una consulta benigna de hostname/HTTP hacia un recolector fuera de banda que usted controle. Nunca abre un shell, escribe archivos, lee datos, persiste o se mueve lateralmente.
sleeppsql de PostgreSQL, encadenado para completar la RCEBT24-10-ONPREM1 / BT24-10-ONPREM2 para appliances autoalojados; las instancias en la nube fueron parcheadas por BeyondTrust).CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HLa cadena produce ejecución remota de código no autenticada como el usuario site en un appliance cuyo propósito principal es intermediar acceso privilegiado hacia sistemas internos, por lo que una única solicitud anónima convierte un host RS o PRA expuesto en un punto de apoyo y un pivote hacia todos los sistemas posteriores que el appliance media.
No requiere credenciales, ni interacción del usuario, ni acceso previo: el atacante es externo y anónimo, y el único requisito previo es la alcanzabilidad de red al endpoint WebSocket del appliance.
La vulnerabilidad se divulgó después de que se descubriera que estaba siendo explotada en el medio silvestre, se añadió al catálogo de Vulnerabilidades Explotadas Conocidas de CISA el 19 de diciembre de 2024, y se ha vinculado públicamente con la brecha de diciembre de 2024 en el Departamento del Tesoro de los EE. UU., atribuida a un actor patrocinado por un estado.
La lógica vulnerable reside en el script shell thin-scc-wrapper del appliance, que lee una clave de sesión controlada por el atacante (gskey) desde una trama WebSocket no autenticada y la pasa hacia la base de datos. La RCE completa es una cadena de tres problemas distintos.
1. Inyección de argumentos en thin-scc-wrapper (CVE-2024-12356). El wrapper pasa la gskey no confiable a un helper sin comillas, por lo que el shell la divide en argumentos antes de que se ejecute echo:
quoted=$(export PHPRC="$BG_app_root/config/php-cli.ini"; echo $gskey | $ingrediRoot/app/dbquote)
Una clave que comienza con -e convierte echo en echo -e, que interpreta escapes de barra invertida y permite al atacante emitir bytes brutos arbitrarios — lo más importante \xC0 — hacia los datos que fluyen a la siguiente etapa. El atacante controla una bandera de un comando existente en lugar de inyectar un comando nuevo, razón por la cual esto es inyección de argumentos (CWE-88) y no inyección clásica de comandos.
2. Omisión del escape de PostgreSQL mediante UTF-8 no válido (CVE-2025-1094). dbquote escapa el valor con pg_escape_string() de PHP, que llama a PQescapeStringInternal() de libpq. Para un carácter multibyte confía en la longitud reportada por pg_encoding_mblen() y copia los bytes del carácter textualmente, sin validarlos:
len = pg_encoding_mblen(encoding, source);
for (i = 0; i < len; i++) /* copy the character */
*target++ = *source++;
Un byte como 0xC0 anuncia un carácter de 2 bytes, por lo que el escapador se traga el siguiente byte como su "continuación" — incluso cuando ese byte es una comilla simple (0x27). Por lo tanto, la secuencia 0xC0 0x27 pasa con un byte de comilla simple sin escapar aún incrustado en la cadena "escapada".
3. psql sale del literal y ejecuta \! (CVE-2025-1094). La cadena "escapada" se canaliza a psql, que no trata 0xC0 0x27 como un solo carácter, por lo que el 0x27 cierra el literal de cadena SQL antes de tiempo y todo lo que sigue se analiza como entrada nueva. El atacante usa eso para alcanzar el metacomando \! de psql, que ejecuta un comando shell como el usuario site:
<0xC0>'; \! <cmd> #
Rapid7 descubrió que la RCE en la práctica siempre depende de CVE-2025-1094, y que es suficiente por sí sola: entregar el byte bruto 0xC0 dentro de una trama WebSocket binaria (en lugar de una trama de texto, donde la validación UTF-8 lo eliminaría) llega a gskey sin modificaciones y desencadena la ruptura SQL sin necesidad del paso de echo -e. Este detector utiliza ese enfoque de trama binaria.
Toda la cadena es alcanzable pre-autenticación a través de un único WebSocket:
/nw (un manejador Tornado en el appliance).Sec-WebSocket-Protocol: ingredi support desk customer thin, enviada codificada en URL como ingredi%20support%20desk%20customer%20thin.Host (mapeada a una compañía instalada) o, como alternativa, desde la cabecera X-Ns-Company. Si ninguna se mapea a una compañía instalada, el appliance cierra el socket antes de que se procese gskey.1, un UUID thinMint, authType 0 (autenticación gskey), y luego la gskey maliciosa (la versión 1 significa que no hay línea de locale_code):1
<thinMint UUID>
0
<0xC0>'; \! <cmd> #
BT24-10 añade una compuerta de validación de entrada que rechaza cualquier gskey no alfanumérica antes de que llegue a dbquote/psql:
elif [[ ! "$gskey" =~ ^[a-zA-Z0-9]{32}$ ]]; then
blog "bad session key given"
...
exit 1
Cualquier clave que contenga 0xC0, una comilla o un metacarácter de shell se rechaza y el script sale, por lo que un appliance parcheado no produce una respuesta 1 failure ni ejecución de comandos. El detector convierte esa diferencia de comportamiento en una diferencial independiente de la salida basada en tiempo que no necesita conectividad saliente del objetivo:
A × 32). Tanto en builds parcheadas como sin parchear esto llega a la consulta de la base de datos y devuelve ['2\n', '1 failure\n'] rápidamente.<0xC0>'; \! sleep <delay> #. En un host vulnerable, psql ejecuta el sleep inyectado, por lo que la respuesta 1 failure se retrasa aproximadamente <delay> segundos. En un host parcheado, la regex de gskey rechaza la clave, el script sale y el socket se cierra rápidamente sin respuesta.Dado que el veredicto se basa en la sincronización de la propia respuesta del appliance, se mantiene incluso cuando el objetivo bloquea DNS/HTTP saliente — un caso donde una devolución de llamada OAST por sí sola daría un falso negativo. El modo de devolución de llamada opcional es solo corroboración, para hosts que sí permiten la salida.
Instale la única dependencia:
pip install -r requirements.txt
Modo diferencial (recomendado, independiente de la salida):
python3 bt_rs_rce_check.py --target rs.example.com --mode differential
Diferencial con corroboración OAST:
python3 bt_rs_rce_check.py --target rs.example.com \
--oast <su-recolector>.oast.pro --mode both
Cuando la cabecera Host no resuelve a un tenant, proporcione un slug explícito, o un archivo de slugs candidatos (uno por línea) que se probarán por turnos mediante la cabecera X-Ns-Company:
python3 bt_rs_rce_check.py --target rs.example.com --company acme
python3 bt_rs_rce_check.py --target rs.example.com --company-list slugs.txt
Opciones clave:
| Opción | Significado |
|---|---|
--target <host> | Host virtual / FQDN del appliance (obligatorio). |
--company <slug> | Slug de tenant explícito, enviado como X-Ns-Company. |
--company-list <archivo> | Archivo de slugs candidatos, probados hasta que uno resuelva. |
--oast <dominio> | Dominio del recolector fuera de banda para corroboración por devolución de llamada. |
--mode {differential,callback,both} | Estrategia de detección (por defecto differential). |
--delay <segundos> | Duración del sleep para la prueba basada en tiempo (por defecto 9). |
--timeout <segundos> | Tiempo de espera del socket por sonda (por defecto 25). |
--header 'H: v' | Cabecera de solicitud bruta adicional; repetible. |
Interpretación del resultado:
VULNERABLE — la respuesta 1 failure inyectada se retrasó aproximadamente --delay segundos en relación con el control. El sleep se ejecutó; la cadena de RCE no autenticada es explotable en este host.PATCHED — el control devolvió 1 failure rápidamente, pero la inyección fue rechazada sin respuesta. La regex de gskey de BT24-10 está activa y la inyección nunca llega al sumidero psql.UNRESOLVED ("la compañía no resolvió") — ninguna de las dos sondas produjo un 1 failure, por lo que el control tampoco llegó al manejador de gskey. Esto no es un resultado parcheado; el appliance cerró el socket porque el Host/compañía no se mapeó a una compañía instalada. Reintente con el host virtual correcto (--target) o un slug válido (--company / --company-list).Aplique BT24-10 (BT24-10-ONPREM1 / BT24-10-ONPREM2) o actualice más allá de la versión 24.3.1. Dado que la cadena también depende del fallo de psql de PostgreSQL (CVE-2025-1094), mantenga también parcheadas las herramientas de cliente de PostgreSQL y restrinja la exposición de red del endpoint WebSocket del appliance cuando sea factible.
psql de PostgreSQL por UTF-8 no válido / inyección SQL, encadenado para completar la RCE.rapid7/metasploit-framework, RCE no autenticada contra BeyondTrust RS/PRA.Este software se proporciona solo para pruebas de seguridad autorizadas e investigación defensiva. Ejecútelo exclusivamente contra sistemas que usted posea o para los que tenga autorización explícita por escrito para probar. Es un detector no destructivo que evita deliberadamente la ejecución de comandos armados, el acceso a datos y la persistencia. Usted es el único responsable de cumplir con todas las leyes aplicables y de obtener la autorización adecuada; los autores no aceptan responsabilidad alguna por el mal uso.