1 puntos por GN⁺ 2025-03-16 | 1 comentarios | Compartir por WhatsApp
  • Las CVE-2025-25291 y CVE-2025-25292, descubiertas en ruby-saml 1.17.0 y versiones anteriores, permiten iniciar sesión como cualquier usuario con una sola firma válida, aumentando el riesgo de toma de cuentas en entornos SAML SSO
  • La vulnerabilidad se origina en una diferencia entre parsers por la cual REXML y Nokogiri pueden interpretar elementos Signature distintos dentro del mismo documento XML durante la ruta de verificación de firmas
  • Al combinarse SignedInfo, SignatureValue, el hash de la assertion y DigestValue desde resultados de parsers diferentes, cada verificación puede pasar, pero puede romperse la conexión entre el hash y la firma
  • Mientras evaluaba volver a incorporar ruby-saml, GitHub realizó un bug bounty y una revisión de Security Lab; encontró una instancia explotable en GitLab y avisó al equipo de seguridad, y actualmente no usa ruby-saml para la autenticación de GitHub
  • Los usuarios deben actualizar a ruby-saml 1.18.0, y las bibliotecas que hacen referencia a ruby-saml, como omniauth-saml, también deben subirse a versiones que apunten a la versión corregida

Impacto de la elusión de autenticación en ruby-saml

  • Se confirmaron 2 vulnerabilidades de alta severidad de elusión de autenticación en ruby-saml 1.17.0 y versiones anteriores
    • CVE-2025-25291
    • CVE-2025-25292
  • Si un atacante cuenta con una sola firma válida generada con la clave usada para verificar la SAML response o la assertion de la organización objetivo, puede crear directamente una SAML assertion e iniciar sesión como cualquier usuario
  • Las posibles fuentes de firma incluyen:
    • una assertion o response firmada de otro usuario con pocos privilegios
    • en algunos casos, metadata firmada de un IdP SAML con acceso público
  • Como resultado, esta vulnerabilidad puede usarse en ataques de toma de cuentas
  • GitHub actualmente no usa ruby-saml para la autenticación, pero evaluó ruby-saml mientras revisaba la posibilidad de volver a usar una biblioteca open source para autenticación SAML
  • ruby-saml también se usa en otros proyectos y productos populares, y GitHub encontró una instancia explotable en GitLab y notificó al equipo de seguridad de GitLab

Por qué GitHub volvió a evaluar ruby-saml

  • GitHub usó ruby-saml hasta 2014, pero en ese momento pasó a una implementación SAML propia porque le faltaban funciones necesarias
  • Más adelante, también hubo reportes de bug bounty en su implementación propia, como CVE-2024-9487, una vulnerabilidad relacionada con assertions cifradas, y GitHub empezó a evaluar la reintroducción de ruby-saml
  • En octubre de 2024 se divulgó CVE-2024-45409, una vulnerabilidad de elusión de autenticación en ruby-saml descubierta por ahacker1
  • Para evaluar más a fondo la posibilidad de migrar a ruby-saml, GitHub inició un bug bounty privado
    • Dio a investigadores seleccionados acceso a un entorno de prueba de GitHub que realizaba autenticación SAML con ruby-saml
    • GitHub Security Lab también revisó la superficie de ataque de ruby-saml

Inconsistencia de verificación creada por dos parsers XML

  • Durante la revisión de código, ahacker1 y GitHub Security Lab confirmaron que en la ruta de verificación de firmas de ruby-saml se usan dos parsers XML en conjunto
    • REXML: parser XML implementado en Ruby puro
    • Nokogiri: ofrece una API que envuelve libxml2, libgumbo, Xerces para JRuby, entre otros, y soporta parsing de XML y HTML
  • La ruta problemática es el método validate_signature en xml_security.rb
  • Este método lee el primer elemento Signature y el SignatureValue real con REXML
    • REXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
  • En cambio, dentro del mismo flujo de verificación, Signature y SignedInfo se vuelven a consultar y se canonicalizan con Nokogiri
    • document.at_xpath('//ds:Signature', 'ds' => DSIG)
    • noko_signed_info_element.canonicalize(canon_algorithm)
  • La assertion se extrae, canonicaliza y hashea con Nokogiri, pero el DigestValue usado para comparar proviene de REXML
  • Los materiales finales de verificación quedan divididos así:
    • la assertion se extrae y canonicaliza con Nokogiri, y luego se hashea
    • el hash contra el que se compara proviene del DigestValue leído por REXML
    • SignedInfo se extrae y canonicaliza con Nokogiri
    • SignatureValue se extrae con REXML

Conexión de seguridad rota en la verificación de firmas SAML

  • Una SAML response transmite en formato XML la información del usuario que inicia sesión desde el IdP hacia el SP
  • Cuando se usa HTTP POST binding, la SAML response viaja hacia el SP pasando por el navegador del usuario, por lo que se necesita verificación de firma para impedir que el usuario altere el mensaje
  • En una SAML response simplificada, la información importante suele estar dentro de Subject y NameID en Assertion
  • Por lo general, puede firmarse la assertion o toda la SAML response
  • Cuando la assertion está firmada, la verificación se realiza en dos pasos:
    • se elimina Signature, se canonicaliza y hashea la assertion, y se compara con DigestValue
    • se canonicaliza SignedInfo y se verifica la firma con SignatureValue
  • En esta vulnerabilidad, aunque ambos pasos pasen por separado, no garantizan que correspondan a los mismos datos
    • el hash puede ser el hash de la assertion real
    • la firma puede ser una firma sobre otro elemento SignedInfo
  • La propiedad de seguridad necesaria es que el contenido hasheado, el hash y la firma estén conectados directamente, y que después de la verificación solo se lea información de la parte realmente verificada

Cómo se construye un exploit real

  • La condición clave era lograr que REXML y Nokogiri vieran Signature distintos en el mismo documento XML, y en la práctica fue posible
  • Durante el bug bounty, ahacker1 primero creó un exploit funcional que aprovechaba diferencias entre parsers
  • GitHub Security Lab creó otro exploit basado en diferencias entre parsers usando el fuzzer de Ruby ruzzy de Trail of Bits
  • El exploit de ejemplo coloca un Signature adicional visible solo para Nokogiri dentro del elemento StatusDetail
  • El flujo de verificación se separa así:
    • se canonicaliza el SignedInfo de la signature que ve Nokogiri
    • se verifica usando el SignatureValue extraído de la signature que ve REXML
    • se canonicaliza y hashea la assertion que Nokogiri encontró por ID
    • se compara el hash con el DigestValue leído por REXML
  • Al final, un SignedInfo válido y una firma válida coinciden entre sí, y la assertion manipulada y su digest calculado también coinciden entre sí, por lo que ruby-saml acepta la assertion

Mitigaciones y límites de detección

  • Al parsear la SAML response con Nokogiri, revisar los errores de parsing puede bloquear algunos exploits privados conocidos actualmente
  • Los errores de parsing de Nokogiri no se generan como excepciones, por lo que hay que revisar directamente el miembro errors del documento parseado
  • El código de ejemplo usa las opciones Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET y genera un error cuando doc.errors.any?
  • Esta no es una corrección completa, pero vuelve inexecutable al menos un exploit
  • No se conocen indicadores de compromiso confiables
    • un posible indicador solo funciona en entornos similares a debug
    • no se divulga porque hacerlo revelaría demasiados detalles de implementación de un exploit funcional
  • El método recomendado de revisión es buscar en el lado del SP inicios de sesión sospechosos, como logins SAML desde direcciones IP que no coincidan con la ubicación esperada del usuario

Dirección de la corrección y objetivos de actualización

  • La corrección inicial no elimina uno de los parsers XML por problemas de compatibilidad de API
  • El problema más fundamental era la separación entre la verificación del hash y la verificación de la firma, y esa separación se volvió explotable mediante diferencias entre parsers
  • Eliminar uno de los parsers XML ya estaba planeado por otros motivos, y probablemente se realice en una versión mayor junto con mejoras adicionales
  • Los usuarios de ruby-saml deben actualizar a 1.18.0, que incluye la corrección
  • También deben revisarse las bibliotecas que usan ruby-saml
    • Ejemplo: omniauth-saml
    • La biblioteca correspondiente debe actualizarse a una versión que apunte a una versión corregida de ruby-saml
  • GitHub Security Lab planea publicar en el futuro un exploit de prueba de concepto en el GitHub Security Lab repository

Cronograma de divulgación y respuesta

  • 2024-11-04: se recibió un reporte de bug bounty que demostraba una elusión de autenticación contra un entorno de prueba de GitHub que evaluaba autenticación SAML con ruby-saml
  • 2024-11-04: comenzó el trabajo para identificar y probar posibles mitigaciones
  • 2024-11-12: se descubrió una segunda elusión de autenticación que anulaba el primer plan de mitigación
  • 2024-11-13: primer contacto con Sixto Martín, maintainer de ruby-saml
  • 2024-11-14: se reportaron a ruby-saml las diferencias entre los dos parsers y el maintainer respondió de inmediato
  • 2024-11-14: el maintainer y ahacker1 empezaron a trabajar en un posible parche
    • una de las ideas iniciales era eliminar uno de los parsers XML, pero no era posible sin romper la compatibilidad hacia atrás
  • 2025-02-04: ahacker1 propuso una corrección sin compatibilidad hacia atrás
  • 2025-02-06: ahacker1 también propuso una corrección compatible hacia atrás
  • 2025-02-12: venció el plazo de 90 días del advisory de GitHub Security Lab
  • 2025-02-16: el maintainer empezó a trabajar en una dirección de corrección que mantiene la compatibilidad hacia atrás y es fácil de entender
  • 2025-02-17: primer contacto con GitLab para coordinar la publicación de ruby-saml y de los productos on-premise de GitLab
  • 2025-03-12: se publicó la versión corregida de ruby-saml

1 comentarios

 
GN⁺ 2025-03-16
Opiniones de Hacker News
  • Creo que la implementación de SAML de GitHub no sirve
    La intención original es traer cuentas personales a la empresa para usarlas, y dentro del sitio de GitHub funciona hasta cierto punto, pero una vez que una app que usa inicio de sesión con GitHub queda aprobada a nivel de organización, no impide que lea la membresía de la organización
    La sesión SAML solo es necesaria cuando esa app trae datos con el token que recibió de ese usuario; en la práctica, las herramientas SAST casi siempre usaban tokens de instancia de la app y mostraban el código con solo tener una cuenta de GitHub dentro de la organización
    Tailscale lo corrigió cuando se lo avisaron, Sonarcloud pidió que no se lo dijeran a nadie, y GitHub respondió unas semanas después que era un “comportamiento totalmente esperado”, pero ninguno de los proveedores informados lo entendía así y la documentación también contradecía la respuesta de GitHub
    Reportar bugs de seguridad no es gratificante ni siquiera cuando los encuentras por casualidad, y cuesta imaginar dedicarse a esto como trabajo

    • La modalidad de “traer cuentas personales a la empresa” también crea problemas que van más allá de la autorización
      GitHub, en contadas ocasiones, usó por defecto la dirección de correo personal al hacer merge de PRs de trabajo, así que si alguien me pregunta, le aconsejo no mezclar lo personal y lo laboral en la misma cuenta, ni en GitHub ni en ningún otro lado
    • También entiendo en parte la postura de los proveedores, porque GitHub hace que implementar esto bien sea extremadamente difícil
      Al crear https://dev.log.xyz, una herramienta de análisis de repositorios/commits/PRs, costó mucho lograr que “solo puedas ver en Devlog lo que puedes ver en GitHub”, y toda la experiencia fue muy frustrante
      El selector de permisos OAuth de GitHub también es demasiado confuso, así que al “iniciar sesión con GitHub” es difícil tener certeza de qué información de qué organización se está compartiendo
    • Esta forma de operar se repite en todos los niveles
      Ya es difícil convencer a desarrolladores jóvenes familiarizados con JavaScript de que la validación del lado del cliente por sí sola no basta, y es todavía más difícil convencer a quienes definen requisitos de negocio y presupuestos
    • La comodidad de las credenciales compartidas y la premisa de que SSO puede mejorar la seguridad se desmoronan con una sola vulnerabilidad como esta
      Cualquier forma de almacén de contraseñas, incluso uno físico o hasta la reutilización de contraseñas, podría terminar siendo más segura
      Al final, el minimalismo volvió a ganar
    • Para quien intente conectar lo anterior con esta investigación de vulnerabilidad, esto no parece estar relacionado
      Dice: “GitHub doesn’t currently use ruby-saml for authentication, but began evaluating the use of the library with the intention of using an open source library for SAML authentication once more”
  • Hace poco tuve que hacer una implementación de SAML, y este título no me sorprende en absoluto
    La especificación SAML en sí es bastante razonable, pero su base, las firmas XML, y más aún la canonicalización XML, son de un nivel de locura enorme, si es que se les puede llamar estándares
    Solo un comité podría crear una especificación tan retorcida y corrompida; no creo que una sola mente humana hubiera podido sostener y combinar ideas tan contradictorias
    Con solo transmitir la firma fuera de banda, SAML habría sido algo agradable de implementar

    • Es mucho peor de lo que dices
      XML significa literalmente eXtensible Markup Language, pero el comité de estandarización de SAML inventó encima su propio lenguaje de mecanismo de extensión
      Montar un protocolo sobre otro protocolo para una cantidad diminuta de datos que no difiere mucho de lo que iría en una cookie de autenticación es un tipo especial de estupidez que solo los comités más grandes y burocráticos pueden producir
    • No sé si SSO sea salvable desde el inicio
      La idea de iniciar sesión por separado en cuentas distintas parece estar bien
      Una estructura que conecta las cuentas entre sí para que puedan ser comprometidas masivamente de una sola vez es fundamentalmente más peligrosa
  • Creo que SAML, y en términos más amplios XML-DSIG, es literalmente el peor de los protocolos de seguridad ampliamente usados
    En general habría que migrar a OAuth a cualquier costo, y al menos yo me negaría a sacar al mercado un producto nuevo que dependa de esto
    Es muy peligroso, y salvo que aparezca un avance en la validación práctica de formatos, cuesta imaginar que esta sea la última vulnerabilidad de DSIG, o la peor

    • Algún día escribiré sobre todas las cosas increíblemente tontas que hace XML DSig
      Ni siquiera toca la criptografía y aun así ya es la cumbre de la mentalidad del software empresarial
      Alguien debería investigar a fondo el desastre de las listas de correo y los organismos de estandarización de cosas como WS-* y OASIS/XACML
    • Va a ser interesante ver cómo Security Cryptography Whatever trata el desorden de SAML de esta semana
    • Si entendí bien, SAML todavía conserva la diferencia de que el proveedor de SSO puede cancelar la sesión
      ¿Es correcto?
  • Uf, nadie debería usar REXML salvo que no haya otra opción
    Acepta parsear XML mal formado y provoca una cantidad infinita de problemas en las etapas posteriores
    Literalmente está parseando XML con expresiones regulares, y es un excelente ejemplo de por qué no se debe hacer eso
    Los proyectos no empezaron a usar Nokogiri por rendimiento, sino por corrección

    • ¿No es un ejemplo de manual?
      No parsear con expresiones regulares algo que no es un lenguaje regular
    • Uno de los riesgos de los asistentes de código con IA es que no necesariamente ven el contexto más amplio de las bibliotecas usadas en bases de código grandes
      Hace poco probé o3, y cada vez que intentaba corregir un problema no relacionado con las bibliotecas de un bloque de código específico, seguía cambiando la biblioteca que usaba ese bloque
      No vi ese comportamiento en Sonnet
      Parece fácil que, durante la corrección, se introduzcan problemas al cambiar a una biblioteca/gem inferior que ya existe en la base de código o en la biblioteca estándar, por lo que las pruebas pasan y tampoco hace falta modificar el Gemfile
  • SAML no es seguro por diseño
    Desde hace tiempo hay artículos que lo explican mejor; por ejemplo, https://joonas.fi/2021/08/saml-is-insecure-by-design/
    Una frase que recuerdo de aquel hilo viejo fue: “firma bytes, no significado”
    Las diferencias entre parsers son previsibles y, a veces, inevitables
    Es muy importante qué se pretende obtener de una respuesta firmada
    Uno de los dilemas del TLS moderno es que a veces quieres confiar en una CA interna, y ese es el camino fácil; pero si aceptas certificados de CA de socios, y tienes varios socios, ya no puedes mirar solo el certificado final: la raíz de la cadena se vuelve igual de importante en la toma de decisiones
    Por eso, si es posible, también recomiendo evitar el algoritmo de firma de AWS
    V4 es seguro en teoría, pero AWS se equivocó dos veces, y SigV1 y SigV3 no eran seguros por diseño, aun así de alguna manera pasaron la revisión de diseño y salieron al público

  • Excelente artículo
    Como también se menciona en el texto, hay que aplaudir mucho a ahacker1
    Está haciendo un trabajo muy sofisticado y valioso para hacer seguras las implementaciones de SAML, y SSOReady también está realmente agradecido por su trabajo
    A principios de esta semana, WorkOS también publicó un buen artículo sobre su colaboración con ahacker1: https://workos.com/blog/samlstorm

  • Hay una parte que dice “encontré este caso explotable de la vulnerabilidad en GitLab y avisé al equipo de seguridad”; para quienes tengan curiosidad, GitLab ya publicó una versión corregida
    https://about.gitlab.com/releases/2025/03/12/patch-release-g...

  • Como lectura relacionada está el artículo de Latacora de 2019, How (not) to sign a JSON object[1]
    En resumen, firmar anidando estructuras de árbol es difícil y está lleno de trampas
    Es más fácil que el sobre contenga el mensaje como una cadena sin procesar y firmar esa cadena sin procesar
    [1]: https://www.latacora.com/blog/2019/07/24/how-not-to/

  • ¿La conclusión más simple aquí no sería buscar la firma en el lugar donde se supone que debe estar?
    No usar un XPath demasiado genérico como “//ds:Signature”, que termine encontrando cualquier firma en una ubicación inesperada

    • Siento que las respuestas a las vulnerabilidades suelen ser demasiado laxas
      No se trata solo de extirpar quirúrgicamente el componente peligroso; hay que tirar un poco al bebé junto con el agua sucia, recortar en masa y tratarlo como quimioterapia
      Cualquier administrador de TI con amor propio debería excluir SAML de sus planes futuros
      Toda la idea de SSO también es sospechosa, y como el parsing de XML ya recibió dos golpes esta semana, habría que evitarlo de ahora en adelante
      ¿Qué tiene de malo una política de reemplazar XML por JSON?
    • Dicho de forma algo extrema, la conclusión es que hay que agarrar un garrote grande e impedir que los desarrolladores web toquen nada cerca de código sensible a la seguridad
      Eso empieza por el diseño, los protocolos y los formatos de datos
      Los hábitos y consideraciones de diseño del desarrollo web común no encajan con lo que requiere el código de seguridad, y a menudo incluso son lo opuesto de lo necesario para escribir código correcto
  • Es un poco molesto que el post del blog explique la vulnerabilidad pero omita justo la diferencia entre parsers que causó el problema
    Es como escribir la introducción de la historia y omitir el clímax

    • En el post del blog de un comentario hermano <https://news.ycombinator.com/item?id=43374972> estaban los detalles relacionados
      Al hacer (...//ds:DigestValue).firstChild.nodeValue, no verificaban que .firstChild fuera un Node, y en el caso problemático era un Comment
      Así que el lado no canonicalizado veía una firma “oculta”, mientras que el lado corregido que descartaba los comentarios veía un Node; cuando dos implementaciones juzgan de forma distinta un documento firmado, pasan cosas nada graciosas
    • Parece que no quería cargar con la responsabilidad de divulgar directamente un zero-day que permitiría el bypass de permisos en innumerables sistemas de todo el mundo antes de que los responsables de esos sistemas tuvieran oportunidad de corregirlo
      Supongo que los detalles concretos saldrán pronto