Neutron, nuestro motor de IA, obtuvo un 97.1% en el benchmark CyberGym de UC Berkeley. Más información

Análisis a fondo

Análisis a fondo

Elusión de autenticación de Xen Orchestra explotada para tomar el control de los hosts (VSA-2026-062)

Un objeto JSON vacío en la cookie authenticationToken elude la autenticación de Xen Orchestra y da acceso de admin; 6.9.2 lo corrige.

Resumen

  • Un objeto JSON vacío en la cookie authenticationToken hace que Xen Orchestra trate la petición como de un administrador autenticado. La búsqueda del token recibe el objeto, lo trata como consulta sin filtro y devuelve el primer token guardado.
  • Xen Orchestra anterior a 6.9.2 en el canal estable y anterior a 6.10.1 en el canal latest está afectado, incluida la Xen Orchestra Appliance. Los paquetes xo-server son 5.211.2 y 5.213.1.
  • El proveedor califica el fallo como Crítico y afirma que se explota activamente. No hay CVE asignado, y el fallo no está en el catálogo Known Exploited Vulnerabilities de CISA a 11 de noviembre de 2026.
  • Actualice a 6.9.2 o 6.10.1, o a xo-server 5.211.2 o 5.213.1. Si no puede actualizar, bloquee la cookie de token vacío en el proxy y retire la instancia de Internet.

¿Estoy afectado?

Todas estas condiciones deben cumplirse:

  • Ejecuta Xen Orchestra anterior a 6.9.2 en el canal estable o anterior a 6.10.1 en el canal latest. La Xen Orchestra Appliance también está afectada.
  • La API REST es accesible por el atacante. La ruta vulnerable es el middleware de autenticación REST, que lee la cookie authenticationToken.
  • La instancia tiene al menos un token de autenticación no caducado en su colección. Un token se crea cuando un usuario inicia sesión y elige permanecer conectado, así que la mayoría de los despliegues activos tienen uno.

El proveedor afirma que el fallo está en Xen Orchestra, no en XCP-ng. El impacto alcanza a los pools gestionados porque Xen Orchestra posee sus credenciales de administrador. Una consola comprometida puede ejecutar comandos en cada host que gestiona.

Xen Orchestra es la consola de gestión de XCP-ng, la alternativa de código abierto a XenServer. El repositorio del proyecto tiene 998 estrellas en GitHub, y la imagen de contenedor comunitaria ronivay/xen-orchestra tiene 2,777,836 descargas en Docker Hub, una señal del número de despliegues que ejecutan la consola.

Canal Versiones afectadas Versiones corregidas
Estable Xen Orchestra anterior a 6.9.2, xo-server anterior a 5.211.2 Xen Orchestra 6.9.2, xo-server 5.211.2
Latest Xen Orchestra anterior a 6.10.1, xo-server anterior a 5.213.1 Xen Orchestra 6.10.1, xo-server 5.213.1

Compruebe la versión del paquete xo-server. En una instalación desde el código fuente, ejecute esto en la raíz del repositorio:

node -p "require('./packages/xo-server/package.json').version"
npm ls xo-server -g --depth=0

En la Xen Orchestra Appliance, abra la página About en la interfaz web y lea la versión de Xen Orchestra. El proveedor también publica las versiones corregidas de xo-server en el aviso. Si la versión es 5.211.2 o 5.213.1, la instancia tiene la corrección; los números anteriores están afectados.

¿Cómo lo corrijo?

Actualice Xen Orchestra a 6.9.2 en el canal estable o 6.10.1 en el canal latest y reinicie el servicio. Las instalaciones desde el código fuente deben actualizar al último master y reconstruir.

  1. En el canal estable, actualice a Xen Orchestra 6.9.2, que incluye xo-server 5.211.2.
  2. En el canal latest, actualice a Xen Orchestra 6.10.1, que incluye xo-server 5.213.1.
  3. En una instalación desde el código fuente, obtenga el último master, ejecute los pasos habituales de instalación y compilación, y reinicie xo-server.
  4. Confirme la nueva versión de xo-server con el comando de «¿Estoy afectado?».
  5. Si usa la appliance, aplique la actualización del proveedor y confirme que la página About muestra 6.9.2 o 6.10.1.

Si no puede actualizar, retire la instancia de Internet y bloquee el token vacío en el proxy. El proveedor enumera tres mitigaciones:

  1. Coloque Xen Orchestra en una red de gestión dedicada y no la exponga a Internet.
  2. Bloquee las direcciones IP y el dominio Tor que el proveedor asocia a la campaña. La lista está en «¿Cómo compruebo si estuve comprometido?».
  3. Añada una regla de WAF o proxy inverso que rechace authenticationToken=j:{} y authenticationToken=j:%7B%7D. El proveedor advierte que las reglas regex se pueden eludir con otras codificaciones, así que trátela como una medida provisional.

Rote todas las credenciales que la consola pueda alcanzar si encuentra cualquier signo de compromiso. Eso incluye el secreto del pool, las contraseñas de root de los hosts, las cuentas de usuario, los secretos OTP, las credenciales de los destinos de copia de seguridad y las contraseñas del servidor de correo. El proveedor recomienda una reinstalación completa tras un compromiso confirmado, porque una limpieza sola no garantiza que una puerta trasera haya desaparecido.

¿Cuál es la causa raíz?

La API REST analiza las cookies con el middleware cookie-parser, que entiende el prefijo j: como JSON. Un valor de cookie authenticationToken=j:%7B%7D se decodifica como el objeto vacío {}. El middleware REST pasa después ese objeto al proveedor de autenticación como token:

  async #authenticateUser(req) {
    const { cookies, ip } = req
    const token = cookies.authenticationToken ?? cookies.token

    if (!token) {
      throw invalidCredentials()
    }

    const { user } = await this.#app.authenticateUser({ token }, { ip })

El proveedor solo comprueba si el valor es verdadero. Un objeto vacío es verdadero, así que la petición continúa:

    this.registerAuthenticationProvider(async ({ token: tokenId }, { ip } = {}) => {
      if (!tokenId) {
        return
      }

      try {
        const token = await app.getAuthenticationToken(tokenId)

getAuthenticationToken espera una cadena o un objeto con un campo id. Lee properties.id sin comprobar el tipo, así que el objeto vacío produce undefined:

  async getAuthenticationToken(properties) {
    const id = typeof properties === 'string' ? properties : properties.id

    const token = await this._getAuthenticationToken(id, properties)

_getAuthenticationToken pasa después el objeto original a la colección de tokens cuando está presente:

  async _getAuthenticationToken(id, properties) {
    const token = await this._tokens.first(properties ?? id)

El first({}) de la colección trata un objeto vacío como una consulta sin condiciones y devuelve el primer token que contiene. Si ese token no ha caducado, el proveedor autentica la petición como su propietario. El atacante no necesita ninguna credencial; el objeto en sí selecciona el registro.

¿Cómo se explota?

  1. El atacante envía una petición HTTP a la API REST de Xen Orchestra con la cookie authenticationToken=j:%7B%7D (o authenticationToken=j:{}).
  2. cookie-parser decodifica el valor j: como el objeto vacío {}.
  3. El middleware REST pasa { token: {} } al proveedor de autenticación. La comprobación de veracidad del proveedor acepta el objeto.
  4. getAuthenticationToken lee properties.id, que es undefined, y reenvía el objeto original a la colección de tokens.
  5. El first({}) de la colección devuelve el primer token. El proveedor comprueba su caducidad y autentica la petición como su propietario.
  6. El atacante llama a /rest/v0/users/me/authentication_tokens con la sesión resultante y recibe identificadores de tokens de inicio de sesión reutilizables.
  7. El atacante usa una sesión de administrador para cambiar la configuración, ejecutar comandos en los pools gestionados y alcanzar los hosts XCP-ng como root.

El proveedor afirma que el exploit puede llevar a la ejecución de código arbitrario en los pools gestionados y a la toma de control completa de un nodo XCP-ng como root. No publica la secuencia exacta de peticiones más allá del patrón de cookie, y los indicadores siguientes son los de la campaña, no un método de detección completo.

¿Cómo se desarrolla el ataque?

Un objeto JSON vacío sustituye el identificador del token, la colección devuelve su primer registro y la petición se convierte en una sesión de administrador que alcanza los hosts gestionados.

flowchart LR A["El atacante envía authenticationToken=j:%7B%7D"] --> B["cookie-parser convierte j: en {}"] B --> C["El middleware REST pasa { token: {} }"] C --> D["El proveedor acepta el objeto verdadero
sin comprobación de cadena"] D --> E["La colección first({})
devuelve el primer token"] E --> F["Sesión como propietario del token
a menudo un administrador"] F --> G["Comandos en los pools gestionados
y los hosts XCP-ng"]

¿Qué puede hacer un atacante?

Un atacante que alcanza la API REST puede autenticarse como el propietario del primer token no caducado. En la práctica es un administrador, porque la sesión puede llamar después a las rutas REST reservadas a administradores.

El impacto incluye:

  • Leer y cambiar la configuración de Xen Orchestra, los usuarios y los tokens de autenticación.
  • Ejecutar comandos en los pools gestionados mediante el acceso a los hosts de Xen Orchestra.
  • Tomar el control de un nodo XCP-ng como root, como describe el proveedor.
  • Recoger la configuración y las credenciales de los proxies, los destinos de copia de seguridad (NFS, S3, Azure, SMB), la cuenta root, el secreto del pool, las cuentas de usuario, los secretos OTP y la configuración del servidor de correo.

Las condiciones son las de «¿Estoy afectado?»: una versión afectada de Xen Orchestra, la API REST accesible por el atacante y al menos un token no caducado en la colección. El proveedor afirma que el fallo se explota activamente, así que una instancia expuesta a Internet debe tratarse como expuesta incluso antes de encontrar un token.

¿Por qué funciona la corrección?

La corrección valida el identificador del token antes de que llegue a la colección y construye la consulta solo con los campos validados. Añade una comprobación de cadena y cambia la búsqueda:

+const isNonEmptyString = value => typeof value === 'string' && value.length !== 0
+
 export default class {
@@
     this.registerAuthenticationProvider(async ({ token: tokenId }, { ip } = {}) => {
-      if (!tokenId) {
+      if (!isNonEmptyString(tokenId)) {
         return
       }
@@
-  async _getAuthenticationToken(id, properties) {
-    const token = await this._tokens.first(properties ?? id)
+  async _getAuthenticationToken(id, userId) {
+    if (!isNonEmptyString(id) || (userId !== undefined && !isNonEmptyString(userId))) {
+      return
+    }
+
+    // build the query from validated fields only, never forward caller-supplied objects
+    const token = await this._tokens.first(userId === undefined ? id : { id, user_id: userId })

getAuthenticationToken ahora extrae solo id y user_id de la entrada y pasa esos valores a la búsqueda privada. Una cookie que se decodifica como {} falla la comprobación de cadena antes de cualquier consulta a la base de datos. Un token de cadena válido sigue funcionando, y una búsqueda compuesta { id, user_id } se conserva. Las claves adicionales que un atacante añada al objeto se ignoran.

La corrección es completa para la ruta descrita. El aviso del proveedor afirma que la API REST, la ruta de inicio de sesión y el método JSON-RPC session.signIn ahora rechazan los identificadores de token no-cadena y vacíos. No cubre el código de terceros que llama al proveedor de tokens con su propio objeto, así que mantenga la instancia fuera de Internet.

¿Cómo compruebo si estuve comprometido?

El proveedor afirma que no hay entrada de registro para un acceso no autenticado exitoso, así que la captura de paquetes es la comprobación más directa. Capture el tráfico en el host de Xen Orchestra o su proxy inverso y busque el patrón de cookie, incluida la forma codificada:

tcpdump -i any -A -s 0 'tcp port 80 or tcp port 443' | grep -iE 'authenticationToken=j:(%7B%7D|\{\})'
grep -iE 'authenticationToken=j:(%7B%7D|\{\})' /var/log/nginx/access.log

La misma petición aparece en los indicadores del proveedor como authenticationToken=j:%7B%7D o authenticationToken=j:{}. Una coincidencia es una pista: los clientes legítimos no deberían enviar un objeto vacío como token.

Compruebe los hosts XCP-ng en busca de los artefactos de la campaña:

xe vm-list params=name-label,platform | grep -B1 'hvm_serial: file'
zgrep -- '-serial file:' /var/log/xensource.log*
ls -la /etc/ld.so.preload /.ustar* /etc/cron.d/zz-ustar*

Los indicadores del proveedor incluyen una máquina virtual llamada pivot2, VM configuradas con el parámetro hvm_serial: file, y los archivos /etc/cron.d/zz-ustar2, /etc/cron.d/zz-ustar, /.ustar5 y /.ustar6. También enumera las direcciones IP 91.211.251.112, 193.148.56.72, 37.221.208.216, 45.66.35.11, 64.65.0.120, 64.65.62.160, 89.47.51.90 y 92.60.37.247, y el dominio Tor ustar3rqxgnskzgnwkgpofgtvxevm6cx3lxdbwt4mndtveqyaohid.onion.

Si encuentra alguno de estos elementos, rote todas las credenciales que la consola pueda alcanzar y reconstruya la instancia. El proveedor afirma que una limpieza sola no garantiza la eliminación de todas las puertas traseras.

Cronología

  • 2026-11-10 08:29 UTC: el commit de corrección 50166fe3 se fusiona mediante la pull request #10748.
  • 2026-11-10: Vates publica VSA-2026-062 y las versiones corregidas Xen Orchestra 6.9.2 y 6.10.1.
  • 2026-11-10: el proveedor informa de explotación activa desde la dirección IP y el dominio Tor enumerados.

Cronología de la corrección, la divulgación y el informe de explotación el 10 de noviembre de 2026
Figura 1: Vates fusionó la corrección, publicó el aviso e informó de explotación activa el mismo día. Fuente: aviso de Vates y pull request #10748, consultados el 11 de noviembre de 2026

Preguntas frecuentes

¿Hay un CVE?

No hay ningún CVE asignado a 11 de noviembre de 2026. El proveedor sigue el problema como VSA-2026-062, y el aviso es el registro autorizado. La ausencia de CVE no hace que el fallo sea menos urgente: el proveedor afirma que se explota activamente.

¿Afecta directamente a los hosts XCP-ng?

El fallo está en Xen Orchestra, no en el software de los hosts XCP-ng. Los hosts están en riesgo porque Xen Orchestra los gestiona con credenciales privilegiadas. Un atacante que toma el control de la consola puede usar ese acceso para ejecutar comandos en los hosts, que es la razón por la que el proveedor describe una toma de control completa del nodo.

¿Qué versión debo instalar?

Instale Xen Orchestra 6.9.2 en el canal estable o 6.10.1 en el canal latest. En una instalación desde el código fuente, actualice al último master y confirme que xo-server es 5.211.2 o 5.213.1. La actualización de la appliance incluye la misma corrección.

Una regla que rechace authenticationToken=j:{} y authenticationToken=j:%7B%7D bloquea la petición de la campaña, pero el proveedor advierte que otras codificaciones pueden eludir una regex estrecha. Use la regla solo hasta que actualice, y mantenga la instancia fuera de Internet.

¿Se explotó como zero-day?

El proveedor afirma que el fallo se explota activamente, pero no dice cuándo comenzó la actividad ni si los ataques precedieron a la corrección. Tampoco dice si la vulnerabilidad se usó como zero-day. Trate la ventana de exposición como desconocida y compruebe los hosts en busca de los artefactos enumerados.

¿Qué debo rotar después de un compromiso?

Rote todas las credenciales que la appliance pueda alcanzar: el secreto del pool, las contraseñas de root de los hosts, las cuentas de usuario de Xen Orchestra, los secretos OTP, la configuración del proxy, las credenciales de los destinos de copia de seguridad NFS, S3, Azure y SMB, y la contraseña del servidor de correo. Reconstruya la appliance desde una imagen sana después de la rotación.

Encuentre consolas Xen Orchestra expuestas con Ostorlab

El descubrimiento de superficie de ataque de Ostorlab inventaría los hosts expuestos a Internet de su entorno, para ver qué consolas Xen Orchestra e interfaces de gestión responden desde fuera de su red antes de corregirlas. Use el inventario para encontrar las consolas que deberían estar en una red de gestión y para priorizar las que siguen expuestas.

Referencias

  • Aviso de Vates VSA-2026-062: https://docs.vates.tech/security-advisories/advisories/2026/vates-sa-2026-062/ (consultado el 11 de noviembre de 2026)
  • Pull request de corrección #10748: https://github.com/vatesfr/xen-orchestra/pull/10748 (consultado el 11 de noviembre de 2026)
  • Commit de corrección 50166fe3: https://github.com/vatesfr/xen-orchestra/commit/50166fe3a49c02069d35ba9046ca4a1f77f4e11d (consultado el 11 de noviembre de 2026)
  • authentication.mjs vulnerable en el commit padre: https://github.com/vatesfr/xen-orchestra/blob/724402733b2381d93372db0183e21df9d2c457f7/packages/xo-server/src/xo-mixins/authentication.mjs (consultado el 11 de noviembre de 2026)
  • rest-api.mjs vulnerable en el commit padre: https://github.com/vatesfr/xen-orchestra/blob/724402733b2381d93372db0183e21df9d2c457f7/packages/xo-server/src/xo-mixins/rest-api.mjs (consultado el 11 de noviembre de 2026)
  • Repositorio de Xen Orchestra: https://github.com/vatesfr/xen-orchestra (consultado el 11 de noviembre de 2026)
  • Imagen de contenedor comunitaria de Xen Orchestra: https://hub.docker.com/r/ronivay/xen-orchestra (consultado el 11 de noviembre de 2026)