- 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
Signaturedistintos dentro del mismo documento XML durante la ruta de verificación de firmas - Al combinarse
SignedInfo,SignatureValue, el hash de la assertion yDigestValuedesde 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_signatureenxml_security.rb - Este método lee el primer elemento
Signaturey elSignatureValuereal con REXMLREXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
- En cambio, dentro del mismo flujo de verificación,
SignatureySignedInfose vuelven a consultar y se canonicalizan con Nokogiridocument.at_xpath('//ds:Signature', 'ds' => DSIG)noko_signed_info_element.canonicalize(canon_algorithm)
- La assertion se extrae, canonicaliza y hashea con Nokogiri, pero el
DigestValueusado 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
DigestValueleído por REXML SignedInfose extrae y canonicaliza con NokogiriSignatureValuese 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
SubjectyNameIDenAssertion - 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 conDigestValue - se canonicaliza
SignedInfoy se verifica la firma conSignatureValue
- se elimina
- 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
Signaturedistintos 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
- Se inspiró en XML roundtrips vulnerabilities, publicado en 2021 por Juho Forsén de Mattermost
- 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
Signatureadicional visible solo para Nokogiri dentro del elementoStatusDetail - El flujo de verificación se separa así:
- se canonicaliza el
SignedInfode la signature que ve Nokogiri - se verifica usando el
SignatureValueextraído de la signature que ve REXML - se canonicaliza y hashea la assertion que Nokogiri encontró por ID
- se compara el hash con el
DigestValueleído por REXML
- se canonicaliza el
- Al final, un
SignedInfová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
errorsdel documento parseado - El código de ejemplo usa las opciones
Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONETy genera un error cuandodoc.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
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
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
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
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
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
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
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
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
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
¿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 parsear con expresiones regulares algo que no es un lenguaje regular
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
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?
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
Al hacer
(...//ds:DigestValue).firstChild.nodeValue, no verificaban que.firstChildfuera un Node, y en el caso problemático era un CommentAsí 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
Supongo que los detalles concretos saldrán pronto