Antecedentes
A principios de octubre, Huntress comenzó a ver cómo actores de amenazas explotaban dos fallas en AhsayCBS, la consola de administración del software de respaldo de Ahsay: CVE-2026-105133, una omisión de autenticación, y CVE-2026-105134, ejecución remota de código sin autenticación como SYSTEM a través de /rps/api/json/UpdateReceivers.do. Nuestra publicación de blog original cubrió la primera ola: webshells, un minero XMRig disfrazado de Microsoft Edge, y un script que ocultaba el minero del Administrador de tareas. Ahsay ya lanzó parches (10.3.4.45 para v10, y 9.15.2.70 para v7 a v9), y verificamos que la versión 10.3.4.45 bloquea el punto de entrada que observamos.
Los servidores de la organización afectada en esta publicación fueron explotados entre el 8 y el 11 de octubre de 2026, antes de que Ahsay lanzara sus parches. Durante nuestra investigación del ataque, analizamos webshells, cargadores y un implante que el actor de amenazas usó durante el ataque.
Esta actividad coincide con la campaña de nuestra primera publicación. La vinculamos a la explotación de AhsayCBS porque los webshells estaban ubicados dentro de los propios directorios web de AhsayCBS, los registros de acceso de AhsayCBS muestran una dirección de origen del exploit que aparece en esa publicación, y uno de los servidores también tenía archivos que coinciden con los indicadores de esa publicación.
Cuando las organizaciones aplican parches, esto mitiga el problema a futuro, pero si ya ocurrió una vulneración, no elimina los archivos que un actor ya haya colocado. Si tus servidores de AhsayCBS estuvieron expuestos antes de aplicar el parche, vale la pena buscar en tu entorno los artefactos mencionados en esta publicación.
Lo que encontramos, en resumen
Nuestro informe inicial describió incidentes que implicaban un ataque rápido y oportunista: un webshell o unos pocos comandos, luego un minero. Sin embargo, este incidente refleja una intrusión mucho más grande y paciente; el operador colocó shells en varios lugares, ejecutó dos mineros distintos, preparó herramientas de tunelización e instaló un implante personalizado, todo en servidores cuya función es almacenar respaldos. El resto de esta publicación analiza cada pieza por separado.
Figura 1: Una descripción general de lo que el operador dejó en los servidores explotados
Webshells, en varias variantes
En general, observamos cinco familias de webshells durante nuestra investigación (ver la tabla a continuación), todas con parámetros y características distintos.
Uno de los webshells más comunes que encontramos en este entorno era pequeño: comparaba una clave codificada en el parámetro p con su propia copia y devolvía un simple 404 si la clave era incorrecta, de modo que un escáner que no conociera la clave vería una página vacía. Con la clave correcta, el parámetro c se pasaba a cmd.exe /c y la salida regresaba codificada en Base64.
La segunda familia de webshells incluía los parámetros w y d, diseñados para escribir bytes decodificados de Base64 en cualquier ruta a la que la cuenta de servicio pudiera acceder, lo que facilitaba colocar más herramientas. Cada archivo llevaba su propia clave (siempre 12 caracteres hexadecimales seguidos de una P mayúscula).
La tercera familia se veía completamente distinta e incluía archivos llamados status_ seguidos de seis letras aleatorias. Estos shells usaban los parámetros auth, cmd, del y rm: estaban diseñados para ejecutar comandos, eliminar de forma recursiva cualquier ruta que se les indicara, y autoeliminarse. Sobre este último punto, los defensores deben tener en cuenta que el shell puede eliminarse a sí mismo después de usarse, por lo que no encontrar el archivo en disco no demuestra que nunca estuvo presente.
Una variante más pequeña de esta familia, llamada st_ seguida de seis letras aleatorias, elimina la autenticación y la función de borrado. Acepta c sin ninguna autenticación, además de un parámetro get=env que imprime el directorio de inicio de la aplicación, la base de Catalina, el sistema operativo y el directorio temporal, y un parámetro rm=1 que elimina el archivo.
Familia | Parámetros y comportamiento |
|---|---|
|
|
| Igual que el anterior, más |
| Secreto |
| API firmada con acciones |
| Cargador para un payload cifrado oculto en un PNG, descrito a continuación. |
Las ubicaciones de las carpetas donde encontramos estos webshells nos dieron más información sobre cómo podría variar el ataque entre sistemas operativos. Los servidores afectados aquí ejecutaban Windows: sin embargo, el exploit también intentó rutas de instalación al estilo Linux, lo que sugiere que el exploit no estaba diseñado para un solo sistema operativo.
Además de webapps\cbs y webapps\rps en la instalación real, encontramos copias en directorios que coincidían con rutas al estilo Linux, incluyendo /opt/AhsayCBS, /usr/local/AhsayCBS y /home/ahsay/AhsayCBS. En uno de los servidores Windows afectados, estas aparecieron como carpetas bajo C:\opt, C:\usr y C:\home, con una carpeta de preparación, C:\ahsay_tf, que contenía otra copia. Esto sugiere que el exploit no fue diseñado para un sistema operativo o ruta de instalación específicos, sino que simplemente intentó cada ubicación probable.
Las copias fuera de la instalación activa no fueron servidas por la aplicación web, por lo que probablemente no se puedan usar como shells en Windows, pero son una señal confiable. Varios shells también se ubicaron en carpetas aleatorias de seis caracteres, cada una con sus propias subcarpetas app, conf y webapps\cbs. Estas imitan la estructura de una instalación de AhsayCBS, pero ninguno de los componentes forma parte de la aplicación real. Algunas de esas carpetas tenían archivos vacíos pb_ marker en la carpeta conf, y en Program Files aparecieron carpetas llamadas ahsay_poc_*, ahsay_tf, ahsay_th y ahsay_t0.
Figura 2: Ubicaciones de los shells en un endpoint con Windows: la instalación activa y las rutas de estilo Linux que el exploit también intenta
Un webshell que se esconde dentro de una imagen
Una de las familias de webshells, AhsayCBS.jsp (en webapps\rps), estaba disfrazada como un servicio de vista previa de imágenes: estaba diseñada para extraer código cifrado de archivos PNG subidos y ejecutarlo en memoria, haciendo que su tráfico pareciera solicitudes normales de imágenes.
El disfraz del webshell como un servicio interno de vista previa de imágenes llegaba incluso a un bloque de comentarios que describía un endpoint de marca de agua. El webshell usaba solo objetos implícitos de JSP y nombres de clase totalmente calificados, lo que significa que compilaría en cualquier contenedor de servlets y en Java Development Kit (JDK) 6 y versiones posteriores.
En la muestra que vimos, el webshell estaba diseñado para funcionar así:
Primero, el operador envía un PNG mediante POST.
Luego la página lee el bit menos significativo de los valores rojo, verde y azul de cada píxel, uno a la vez.
Une esos bits y los empaqueta en bytes, donde el primer bit leído se convierte en el bit más significativo de cada byte.
Eso reconstruye una longitud de cuatro bytes en formato big-endian, seguida de esa cantidad de bytes de payload, que luego descifra con AES.
La clave está codificada de forma fija, y el cifrado es la transformación
AESpredeterminada de Java (que equivale al modo ECB con relleno PKCS5).
En la primera solicitud, los bytes descifrados se cargan como una clase Java mediante un pequeño ClassLoader personalizado y se almacenan en el contexto de la aplicación bajo el nombre de atributo QuCh, y la página responde con su imagen de portada integrada. En solicitudes posteriores, crea una instancia de esa clase, le entrega la solicitud, un búfer de salida y los datos descifrados mediante llamadas a equals(), y llama a toString() para ejecutarla. Lo que sea que la clase produzca se vuelve a cifrar y se escribe en un nuevo PNG.
Pasar argumentos a través de equals() es una convención usada por algunos frameworks de webshells de código abierto en memoria, aunque no hemos vinculado este caso a un framework específico. El efecto práctico es que la funcionalidad llega por solicitud y vive en memoria. El registro de acceso mostraba lo que parecía ser tráfico de imágenes, y no había ningún parámetro c o p que buscar. Analizamos el cargador, no la clase que recibe, por lo que no podemos confirmar qué ejecutó realmente el operador a través de él.
Figura 3: Cómo AhsayCBS.jsp convierte una imagen subida en código en ejecución
Un webshell con una API firmada
No todos los shells eran de una sola línea. nsmind.jsp, también en webapps\rps, se comportaba como un pequeño servicio de administración remota.
En la muestra que vimos, cada solicitud debe llevar un encabezado X-Auth-Token que coincida con una clave API codificada de forma fija, además de X-Timestamp, X-Nonce y un encabezado X-Signature que contiene un HMAC-SHA256 de los parámetros de la solicitud. La marca de tiempo debe estar dentro de los cinco minutos del reloj del servidor, y cada nonce se almacena en el contexto de la aplicación para que una solicitud capturada no se pueda reproducir. Una solicitud que falla alguna verificación recibe un breve error en JSON, de modo que la página se comporta como una API normal que rechaza llamadas no autenticadas.
Detrás de las verificaciones, el parámetro action selecciona exec, cd, upload o download. exec ejecuta el comando a través de cmd.exe /c (después de chcp 65001, para que la salida vuelva en UTF-8) y hace seguimiento del directorio de trabajo entre llamadas, de modo que el operador obtiene un shell persistente. upload y download mueven archivos en fragmentos usando un parámetro offset, con un límite de 4 GB por archivo, lo que significa que un archivo grande se puede extraer del servidor mediante muchas solicitudes pequeñas.
En un servidor que almacena copias de seguridad, esta acción de descarga es la parte que preocupa: es un canal de exfiltración listo para usar que parece tráfico de API normal. No vimos registros que mostraran el uso de download, por lo que no podemos decir qué, si acaso, se sustrajo.
Los archivos también mostraron que el operador fue iterando. En la misma carpeta rps encontramos tres carpetas con nombre de marca de tiempo, cada una con una sola copia de un shell. La más antigua contenía el shell simple cmd, del y rm descrito anteriormente, y las dos más recientes contenían nsmind.jsp, lo que sugiere que el operador pasó de un shell básico a esta herramienta. También encontramos copias idénticas byte por byte de nsmind.jsp y AhsayCBS.jsp en dos de los servidores, lo que apunta a un solo operador o un solo script que actúa en varios endpoints.
Un segundo minero, con su propia persistencia
El minero con temática de Edge analizado en nuestro blog inicial no fue el único: también encontramos otro minero en nuestra investigación de seguimiento.
El minero XMRig que observamos (kvm-agent.exe) está configurado como una compilación estándar de XMRig: nivel de donación 0, acceso MSR habilitado (la misma capacidad que ofrece el controlador WinRing0 mencionado en la primera publicación) y conexiones TLS en el puerto 443 hacia los pools de SupportXMR. La billetera y la contraseña eran distintas del pool Kryptex y el usuario indicados en la primera publicación, lo que nos lleva a creer que se trataba de un minero separado y no de una segunda copia del primero. Encontramos el mismo archivo, con el mismo hash, en dos servidores, y la tarea en ambos se creó la misma noche y se editó con segundos de diferencia entre sí, lo que de nuevo apunta a un solo operador o un solo script que actúa en varios endpoints.
Durante nuestro análisis, encontramos una tarea programada llamada kvm que se ejecutaba como SYSTEM cada minuto. Su comando usaba un tasklist para verificar si kvm-agent.exe se estaba ejecutando y, si no era así, lo iniciaba desde C:\Windows\SysWOW64\kvm-agent. Esto significa que si el minero era detenido, volvería a estar activo en menos de un minuto. La hora de inicio de la tarea se configuró mucho antes de la intrusión (haciéndola parecer más antigua de lo que realmente era en los listados de tareas).
Figura 4: La tarea programada kvm (hora de inicio omitida)
Un implante de Python que solo se ejecuta donde fue almacenado
El archivo más interesante que obtuvimos fue main.py, que se encuentra en una distribución de Python incorporada bajo el perfil SYSTEM y se inicia desde una clave Run a través de explorer.exe. El script es solo un envoltorio: Base64, luego lzma, y después un objeto de código Python serializado que pasa a exec. Al decodificarlo sin ejecutarlo, se ve que el código se compiló a partir de un archivo llamado heap_shellcode_runner_bound_weaponized.py.
El código calcula el SHA-256 del MachineGuid del equipo y busca un archivo con ese nombre junto a main.py. Según el bytecode, si falta el archivo marcador, el script se detiene. Eso evitaría que el implante se ejecute en un sandbox o en la máquina de un analista, porque el marcador solo existe en el equipo para el que fue preparado. No encontramos un archivo marcador en la carpeta que recolectamos, así que o no formaba parte de lo que obtuvimos o fue eliminado. Si el marcador está presente, el script copia un blob de 673 KB a un heap ejecutable (HeapCreate, HeapAlloc, RtlMoveMemory) y lo inicia en un nuevo hilo con CreateThread.
Figura 5: El flujo de ejecución del implante, reconstruido a partir de un análisis estático
Figura 6: La lógica decodificada, reconstruida a partir del objeto de código
El blob comienza con un pequeño bootstrap que llama a un cargador reflexivo al final del blob, el cual a su vez mapea una DLL x64 que lleva incorporada. La DLL está escrita en C++ con una biblioteca JSON e importa muy poco: bcrypt, KERNEL32, OLEAUT32 y SHLWAPI, y las funciones de red se resuelven en tiempo de ejecución. No tiene tabla de exportación, ni ruta de PDB, ni recurso de versión, por lo que se eliminaron las pistas habituales de atribución.
Entre sus cadenas se incluyen la URL de C2 hxxps://creepexplosiondarknight[.]com/d, un tipo de contenido JSON, un mutex y fragmentos de cadenas (dns.quad con 9.net, y dns.goog) coherentes con consultas DNS sobre HTTPS a través de Quad9 y Google. También lee el MachineGuid y la información de versión de Windows, lo cual es coherente con la identificación del equipo. No hemos identificado la familia de malware ni hemos observado la ejecución del implante, por lo que lo que hace después de registrarse sigue siendo una pregunta abierta.
Figura 7: Cadenas seleccionadas de la DLL incorporada (C2 ofuscado)
El resto del conjunto de herramientas
Alrededor de esas piezas, el mismo entorno contenía las herramientas de tunelización gost y chisel, ubicadas en carpetas .st<id> como wust<id>a.exe y wust<id>b.exe, las herramientas de acceso remoto AnyDesk, Atera y Splashtop, una cuenta local llamada wdguest, y un paquete de Mimikatz en el disco. También vimos exclusiones de Microsoft Defender que cubrían C:\ProgramData, C:\Windows, carpetas temporales, los procesos del dropper y el proceso del servicio AhsayCBS (cbssvcX64.exe). Estas exclusiones habrían reducido la capacidad de Defender para escanear archivos en esas ubicaciones y archivos a los que accedían los procesos excluidos, lo que crea amplios puntos ciegos para las herramientas del operador.
Servidores de respaldo como objetivo
Todo lo anterior se ejecutó en servidores cuyo propósito es almacenar o gestionar copias de respaldo. En un servidor Veeam relacionado dentro del mismo entorno, también vimos un comando consistente con el módulo de robo de credenciales de Veeam de NetExec: un script de PowerShell, aparentemente lanzado de forma remota a través de WMI, que consultaba la base de datos PostgreSQL de VeeamBackup en busca de credenciales almacenadas y las descifraba con DPAPI. El software de respaldo almacena credenciales de hipervisores, repositorios y cuentas de dominio. No podemos decir cuántas de esas credenciales fueron obtenidas, pero la combinación de un punto de apoyo en Ahsay con una consulta de credenciales de Veeam sugiere que el operador estaba interesado en la infraestructura de respaldo de forma intencional, no accidental.
Qué revisar
Aplicar el parche cierra el punto de entrada. No limpia lo que ya ocurrió, así que en cualquier servidor que estuvo expuesto antes del hotfix, busca lo siguiente:
Archivos
.jspinesperados bajowebapps\cbsywebapps\rps, especialmente nombres aleatorios y los patronesstatus_yst_, y solicitudes aUpdateReceivers.doen los registros de acceso de AhsayCBSCarpetas llamadas
ahsay_*,home,opt,srvousren la raíz de la unidad o en Program Files, y carpetas con nombre de marca de tiempo (año-mes-día-hora-minuto-segundo) junto a los shells, que en algunos casos contenían una sola copia de unoTareas programadas y servicios que tú no creaste, incluida una tarea
SYSTEMcon una repetición de un minuto que comprueba la existencia de un proceso contasklistExclusiones de Defender que tú no agregaste, especialmente para
C:\ProgramData,C:\Windowsy el proceso del servicio AhsayCBSUna instalación de Python incrustada bajo el perfil
SYSTEMy una clave Run que la inicia
Algunos comportamientos permiten detecciones útiles incluso cuando los archivos ya no están:
python.exeopythonw.exeejecutándose desde una ruta bajo el perfilSYSTEMSolicitudes salientes a resolutores públicos de DNS-over-HTTPS desde un proceso que no es un navegador
Nuevos archivos
.jspcreados bajo los directorios web de AhsayCBSEl patrón padre-hijo que nuestra regla Sigma anterior ya busca, donde
cbssvcX64.exegenera un shell de comandos.
Si encuentras algo de lo anterior, recomendamos una reimagen completa a partir de un respaldo confiable y rotar las credenciales almacenadas en el servidor o accesibles desde él, incluidas las guardadas en AhsayCBS y en cualquier software de respaldo en la misma red.
Indicadores de compromiso (IOC)
Elemento | Descripción |
|---|---|
| Comando y control para el implante basado en Python |
Wallet: Password: | Pools de minería y billetera usados por |
SHA256:
SHA256:
Task XML SHA256:
Scheduled task: | Minero XMRig, su configuración y su persistencia (SYSTEM, cada minuto, con hora de inicio retroactiva) |
SHA256:
Payload decodificado (objeto marshal) SHA256:
Blob incrustado (bootstrap, DLL x64 y cargador) SHA256:
DLL x64 SHA256:
Cola del cargador reflectivo SHA256:
| Cargador de Python y el payload extraído de él (compilado a partir de heap_shellcode_runner_bound_weaponized.py). El archivo marcador se nombra con el SHA-256 del MachineGuid del endpoint. |
SHA256:
AES key: Application attribute: | Cargador JSP que lee un payload cifrado a partir de datos de píxeles PNG y lo ejecuta en memoria |
SHA256:
X-Auth-Token / API key: HMAC-SHA256 secret: | Webshell JSP con solicitudes firmadas, ejecución de comandos y carga y descarga de archivos fragmentados |
SHA256:
Key parameter | Webshell JSP con clave que ejecuta comandos y devuelve la salida codificada en Base64. Los shells relacionados comparten este código y llevan claves diferentes, y algunos agregan una función de escritura de archivos ( |
| Claves fijas en el código (parámetro |
Fixed secrets: SHA256
SHA256
SHA256
| Shells |
| Secretos de autenticación de 32 caracteres por endpoint encontrados en los shells |
SHA256:
SHA256:
| Herramientas de tunelización almacenadas en carpetas .st<id> como wust<id>a.exe y wust<id>b.exe |
Carpetas: Cuenta local: | Carpetas creadas en la raíz de la unidad o en Program Files durante la explotación, y una cuenta local creada por el operador |
Mapeo de MITRE ATT&CK
Táctica | ID de técnica | Nombre de técnica | Descripción |
|---|---|---|---|
Acceso inicial | T1190 | Explotar aplicación expuesta al público | Encadenó CVE-2026-105133 y CVE-2026-105134 contra una instancia de AhsayCBS expuesta a internet |
Ejecución | T1059.003 | Intérprete de comandos y scripts: Windows Command Shell | Los webshells ejecutaron comandos mediante |
T1059.006 | Intérprete de comandos y scripts: Python |
| |
Persistencia | T1505.003 | Componente de software de servidor: web shell | Cinco familias de webshells JSP en |
T1053.005 | Tarea o trabajo programado: tarea programada | La tarea | |
T1547.001 | Ejecución automática al arrancar o iniciar sesión: claves Run del registro / carpeta de inicio | Una clave Run iniciaba el implante de Python | |
Evasión de defensas | T1562.001 | Afectar defensas: deshabilitar o modificar herramientas | Exclusiones de Defender para rutas del sistema, extensiones de archivo y el proceso del servicio de AhsayCBS |
T1027 | Archivos o información ofuscados |
| |
T1027.003 | Archivos o información ofuscados: esteganografía |
| |
T1620 | Carga reflexiva de código | El implante cargó una DLL x64 mediante un cargador reflexivo; el cargador JSP definió una clase Java en memoria | |
T1480.001 | Barreras de ejecución: codificación según el entorno | El implante requería un archivo marcador nombrado según el SHA-256 del | |
Acceso a credenciales | T1555 | Credenciales de almacenes de contraseñas | Un comando compatible con el módulo Veeam de NetExec consultó credenciales de respaldo almacenadas |
Comando y control | T1071.001 | Protocolo de capa de aplicación: protocolos web | El implante usó HTTPS hacia |
T1572 | Tunelización de protocolo | Herramientas de tunelización | |
T1219 | Software de acceso remoto | AnyDesk, Atera y Splashtop | |
Impacto | T1496.001 | Secuestro de recursos: secuestro de cómputo |
|
Referencias
Actores de amenazas explotan fallas críticas de AhsayCBS para implementar webshells y el criptominero XMRig:https://www.huntress.com/blog/ahsaycbs-flaws-exploit