- As CVE-2025-25291 e CVE-2025-25292, encontradas no ruby-saml 1.17.0 ou inferior, aumentam o risco de sequestro de contas em ambientes SAML SSO ao permitir login como qualquer usuário com apenas uma assinatura válida
- A vulnerabilidade decorre de uma diferença entre parsers pela qual REXML e Nokogiri podem interpretar elementos
Signaturediferentes no mesmo documento XML dentro do fluxo de verificação de assinatura - Como
SignedInfo,SignatureValue, o hash da assertion eDigestValueacabam sendo combinados a partir dos resultados de parsers diferentes, cada verificação pode passar, mas a ligação entre hash e assinatura pode se romper - O GitHub conduziu bug bounty e revisão do Security Lab ao reavaliar a reintrodução do ruby-saml, encontrou instâncias exploráveis no GitLab e notificou a equipe de segurança; atualmente, a autenticação do GitHub não usa ruby-saml
- Os usuários devem atualizar para o ruby-saml 1.18.0, e bibliotecas que dependem de ruby-saml, como
omniauth-saml, também devem ser atualizadas para versões que referenciem a correção
Impacto do bypass de autenticação no ruby-saml
- Foram confirmadas 2 vulnerabilidades de bypass de autenticação de alta gravidade no ruby-saml 1.17.0 ou inferior
- CVE-2025-25291
- CVE-2025-25292
- Se o atacante tiver uma única assinatura válida gerada com a chave usada para validar respostas SAML ou assertions da organização-alvo, ele pode criar diretamente uma SAML assertion e entrar como qualquer usuário
- As possíveis origens dessa assinatura incluem:
- assertion ou response assinada de outro usuário com privilégios baixos
- em alguns casos, metadata assinada de um SAML IdP acessível publicamente
- Na prática, essa vulnerabilidade pode ser usada em ataques de sequestro de conta
- O GitHub atualmente não usa ruby-saml para autenticação, mas avaliou o ruby-saml enquanto estudava voltar a usar uma biblioteca open source para autenticação SAML
- O ruby-saml também é usado em outros projetos e produtos populares, e o GitHub encontrou uma instância explorável no GitLab e notificou a equipe de segurança do GitLab
Por que o GitHub voltou a analisar o ruby-saml
- O GitHub usou ruby-saml até 2014, mas na época faltavam recursos necessários, então migrou para uma implementação própria de SAML
- Depois disso, a implementação própria também recebeu relatórios de bug bounty sobre falhas como CVE-2024-9487, uma vulnerabilidade relacionada a assertions criptografadas, e o GitHub passou a considerar reintroduzir o ruby-saml
- Em outubro de 2024, foi divulgada a vulnerabilidade de bypass de autenticação CVE-2024-45409 no ruby-saml, descoberta por ahacker1
- Para avaliar com mais cuidado a possibilidade de migrar para ruby-saml, o GitHub iniciou um bug bounty privado
- Pesquisadores selecionados receberam acesso a um ambiente de teste do GitHub que fazia autenticação SAML com ruby-saml
- O GitHub Security Lab também revisou a superfície de ataque do ruby-saml
Divergência de validação criada por dois parsers XML
- Durante a revisão de código, ahacker1 e o GitHub Security Lab confirmaram que dois parsers XML eram usados juntos no fluxo de verificação de assinatura do ruby-saml
- REXML: parser XML implementado em Ruby puro
- Nokogiri: fornece APIs sobre libxml2, libgumbo e Xerces para JRuby, com suporte a parsing de XML e HTML
- O caminho problemático era o método
validate_signatureemxml_security.rb - Esse método lê o primeiro elemento
Signaturee oSignatureValuereal com REXMLREXML::XPath.first(@working_copy, "//ds:Signature", {"ds"=>DSIG})
- Já no mesmo fluxo de verificação,
SignatureeSignedInfosão buscados novamente e canonicalizados com Nokogiridocument.at_xpath('//ds:Signature', 'ds' => DSIG)noko_signed_info_element.canonicalize(canon_algorithm)
- A assertion é extraída, canonicalizada e hasheada com Nokogiri, mas o
DigestValueusado na comparação vem do REXML - No fim, o material de verificação se divide assim:
- a assertion é extraída e canonicalizada com Nokogiri, depois hasheada
- o hash usado na comparação vem do
DigestValuelido pelo REXML SignedInfoé extraído e canonicalizado com NokogiriSignatureValueé extraído com REXML
A ligação de segurança quebrada na verificação de assinatura SAML
- A SAML response transmite, do IdP para o SP, informações de login do usuário em formato XML
- Quando se usa HTTP POST binding, a SAML response passa pelo navegador do usuário antes de chegar ao SP, então é necessária verificação de assinatura para impedir que o usuário altere a mensagem
- Em uma SAML response simplificada, as informações mais importantes normalmente ficam em
SubjecteNameIDdentro deAssertion - Em geral, pode-se assinar a assertion ou a SAML response inteira
- Quando a assertion é assinada, a verificação ocorre em duas etapas
- a assertion sem
Signatureé canonicalizada e hasheada para comparação comDigestValue SignedInfoé canonicalizado e a assinatura é verificada comSignatureValue
- a assertion sem
- Nesta vulnerabilidade, mesmo que as duas etapas passem separadamente, isso não garante que tratem dos mesmos dados
- o hash pode ser o hash da assertion real
- a assinatura pode ser uma assinatura sobre outro elemento
SignedInfo
- A propriedade de segurança necessária é que o conteúdo hasheado, o hash e a assinatura estejam diretamente ligados entre si, e que, após a verificação, as informações sejam lidas apenas da parte efetivamente validada
Como o exploit real é montado
- A condição central era fazer REXML e Nokogiri verem
Signaturediferentes no mesmo documento XML, e isso de fato era possível - Durante o bug bounty, ahacker1 criou primeiro um exploit funcional baseado em diferenças entre parsers
- A inspiração veio de XML roundtrips vulnerabilities, publicado em 2021 por Juho Forsén, da Mattermost
- O GitHub Security Lab criou outro exploit baseado em diferenças entre parsers usando o fuzzer para Ruby ruzzy da Trail of Bits
- Um exploit de exemplo coloca uma
Signatureextra, visível apenas ao Nokogiri, dentro do elementoStatusDetail - O fluxo de verificação então se separa da seguinte forma
- o
SignedInfoda signature vista pelo Nokogiri é canonicalizado - a verificação é feita com o
SignatureValueextraído da signature vista pelo REXML - a assertion encontrada por ID pelo Nokogiri é canonicalizada e hasheada
- o hash é comparado com o
DigestValuelido pelo REXML
- o
- No fim, um
SignedInfoválido e uma assinatura válida continuam combinando entre si, e a assertion manipulada também combina com o digest calculado, fazendo o ruby-saml aceitar a assertion
Mitigações e limites de detecção
- Verificar erros de parsing ao processar a SAML response com Nokogiri pode bloquear parte dos exploits privados atualmente conhecidos
- Erros de parsing do Nokogiri não são lançados como exceção, então é preciso checar diretamente o membro
errorsdo documento parseado - O código de exemplo usa as opções
Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONETe gera erro quandodoc.errors.any? - Essa abordagem não é uma correção completa, mas impede pelo menos um exploit conhecido
- Não são conhecidos indicadores de comprometimento confiáveis
- um indicador potencial só funciona em ambientes parecidos com debug
- ele não é divulgado porque isso revelaria detalhes demais de implementação de um exploit funcional
- O método recomendado de verificação é procurar, no lado do SP, logins SAML suspeitos, como acessos vindos de endereços IP incompatíveis com a localização esperada do usuário
Direção da correção e o que atualizar
- A correção inicial não removeu um dos parsers XML por questões de compatibilidade de API
- O problema mais fundamental era a separação entre verificação de hash e verificação de assinatura, e essa separação se tornou explorável por meio de diferenças entre parsers
- A remoção de um dos parsers XML já estava planejada por outros motivos e pode acontecer em uma versão principal junto com melhorias adicionais
- Usuários de ruby-saml devem atualizar para a versão 1.18.0, que inclui a correção
- Bibliotecas que usam ruby-saml também precisam ser verificadas
- Ex.: omniauth-saml
- Essas bibliotecas devem ser atualizadas para versões que apontem para uma versão corrigida do ruby-saml
- O GitHub Security Lab pretende publicar futuramente um exploit de prova de conceito no repositório do GitHub Security Lab
Cronograma de divulgação e resposta
- 2024-11-04: foi recebido um relatório de bug bounty demonstrando bypass de autenticação no ambiente de testes do GitHub que avaliava autenticação SAML com ruby-saml
- 2024-11-04: começou o trabalho de identificar e testar possíveis mitigações
- 2024-11-12: foi descoberto um segundo bypass de autenticação que anulava o primeiro plano de mitigação
- 2024-11-13: ocorreu o primeiro contato com o maintainer do ruby-saml, Sixto Martín
- 2024-11-14: a diferença entre os dois parsers foi reportada ao ruby-saml, e o maintainer respondeu imediatamente
- 2024-11-14: o maintainer e ahacker1 começaram a trabalhar em possíveis patches
- uma das ideias iniciais era remover um dos parsers XML, mas isso não era possível sem quebrar compatibilidade retroativa
- 2025-02-04: ahacker1 propôs uma correção incompatível com versões anteriores
- 2025-02-06: ahacker1 também propôs uma correção compatível com versões anteriores
- 2025-02-12: expirou o prazo de 90 dias do advisory do GitHub Security Lab
- 2025-02-16: o maintainer começou a trabalhar em uma correção que preservasse compatibilidade retroativa e fosse mais fácil de entender
- 2025-02-17: ocorreu o primeiro contato com o GitLab para coordenar o lançamento do ruby-saml corrigido e da versão do produto on-premise do GitLab
- 2025-03-12: foi lançada a versão corrigida do ruby-saml
1 comentários
Opiniões no Hacker News
Acho que a implementação de SAML do GitHub é inútil
A intenção original é trazer contas pessoais para o ambiente corporativo e usá-las ali; dentro do site do GitHub isso funciona até certo ponto, mas, depois que um app conectado via GitHub é aprovado no nível da organização, nada impede que ele leia a associação à organização
A sessão SAML só é necessária quando esse app busca dados com o token que recebeu daquele usuário; na prática, ferramentas de SAST quase sempre usavam tokens de instância de app e mostravam o código bastando haver uma conta GitHub dentro da organização
A Tailscale corrigiu quando foi avisada, a Sonarcloud pediu para não contar a ninguém, e o GitHub respondeu semanas depois que era “comportamento totalmente esperado”, mas nenhum dos fornecedores avisados entendia a coisa dessa forma, e a documentação também contradizia a resposta do GitHub
Relatar bugs de segurança não é recompensador nem quando você os encontra por acaso, e é difícil imaginar fazer disso uma profissão
O GitHub, muito ocasionalmente, chegou a usar o endereço de e-mail pessoal como padrão ao fazer merge de PRs de trabalho, então, se alguém me perguntar, eu recomendaria não misturar uso pessoal e profissional na mesma conta, seja no GitHub ou em qualquer outro lugar
Ao criar uma ferramenta de análise de repositórios/commits/PRs, https://dev.log.xyz, deu muito trabalho fazer com que “só o que você consegue ver no GitHub também possa ser visto no Devlog”, e a experiência toda foi muito frustrante
O seletor de permissões OAuth do GitHub também é confuso demais, então é difícil ter certeza de quais informações de qual organização estão sendo compartilhadas ao “entrar com GitHub”
Já é difícil convencer jovens desenvolvedores familiarizados com JavaScript de que só validação do lado do cliente não basta; para empresários que definem requisitos de funcionalidade e orçamento, é ainda mais difícil
Qualquer forma de cofre de senhas — até mesmo um cofre físico ou a reutilização de senhas — pode acabar sendo mais segura
No fim, o minimalismo venceu de novo
O texto diz: “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”
Recentemente precisei fazer uma implementação de SAML, e esse título não me surpreende nem um pouco
A especificação SAML em si é bastante razoável, mas XML Signature, que está na base dela — e, indo além, a canonicalização de XML — é de um nível realmente insano, se é que dá para chamar isso de padrão
Só um comitê conseguiria criar uma especificação tão distorcida e corrompida; parece que a mente de uma única pessoa não conseguiria sustentar e combinar ideias tão contraditórias assim
Se a assinatura fosse simplesmente transmitida fora da banda, SAML teria se tornado algo agradável de implementar
XML é literalmente eXtensible Markup Language, mas o comitê de padronização do SAML ainda inventou em cima disso sua própria linguagem de mecanismo de extensão
Colocar um protocolo em cima de outro protocolo para um conjunto minúsculo de dados, não muito diferente do que iria em um cookie de autenticação, é um tipo especial de estupidez que só os maiores e mais burocráticos comitês conseguiriam produzir
A ideia de fazer login separadamente em contas diferentes parece ok
Uma estrutura que conecta contas entre si para que possam ser comprometidas em massa de uma só vez é fundamentalmente mais perigosa
SAML, e mais amplamente XML-DSIG, é literalmente o pior entre os protocolos de segurança amplamente usados, na minha opinião
Em geral, deve-se migrar para OAuth a qualquer custo e, no mínimo, eu me recusaria a lançar um produto novo no mercado que dependesse disso
É muito perigoso, e é difícil imaginar que esta será a última vulnerabilidade de DSIG, ou a pior, a menos que haja algum avanço em validação prática de formatos
Nem toquei em criptografia, e ainda assim isso já é o ápice do pensamento típico de software corporativo
Alguém precisa se aprofundar no horror das listas de discussão e dos órgãos de padronização de coisas como WS-* e OASIS/XACML
É isso mesmo?
Argh, ninguém deveria usar REXML a menos que não haja outra opção
Ele aceita de bom grado fazer parsing de XML inválido, causando infinitos problemas nas etapas seguintes
Literalmente faz parsing de XML com expressões regulares, e é um ótimo exemplo de por que não se deve fazer isso
Os projetos não começaram a usar Nokogiri por desempenho, mas por corretude
Não faça parsing com expressões regulares de algo que não é uma linguagem regular
Testei o o3 recentemente e, sempre que ele tentava corrigir um problema não relacionado à biblioteca de um bloco de código específico, continuava trocando a biblioteca usada naquele bloco
Não vi esse comportamento no Sonnet
Parece fácil um problema se infiltrar quando, durante a correção, o código é trocado por uma biblioteca/gem inferior que já existe na base de código ou na biblioteca padrão, de modo que os testes passam e nenhuma mudança no Gemfile é necessária
SAML é inseguro por design
Já existem textos antigos que explicam isso melhor; por exemplo, https://joonas.fi/2021/08/saml-is-insecure-by-design/
A frase que ficou na memória daquele thread antigo foi “assine bytes, não significado”
Diferenças entre parsers são previsíveis e, às vezes, inevitáveis
O que você pretende obter de uma resposta assinada é muito importante
Um dos dilemas do TLS moderno é que, às vezes, você quer confiar em uma única CA interna, e esse é o caminho fácil; mas, ao aceitar o certificado de CA de um parceiro, se houver vários parceiros, você já não pode olhar apenas para o certificado final, e a raiz da cadeia passa a ser igualmente importante na tomada de decisão
Por isso, quando possível, também recomendo evitar os algoritmos de assinatura da AWS
A V4 é teoricamente segura, mas a AWS errou duas vezes, e SigV1 e SigV3 eram inseguras por design, ainda assim de algum modo passaram pela revisão de design e foram lançadas publicamente
Excelente texto
Como o próprio artigo menciona, é preciso aplaudir muito ahacker1
Ele vem fazendo um trabalho extremamente sofisticado e valioso para tornar implementações de SAML seguras, e a SSOReady é realmente grata pelo trabalho dele
No começo desta semana, a WorkOS também publicou um bom texto sobre a colaboração com ahacker1: https://workos.com/blog/samlstorm
Há um trecho dizendo “encontramos um caso explorável dessa vulnerabilidade no GitLab e notificamos a equipe de segurança”; para quem ficou curioso, o GitLab já lançou uma correção
https://about.gitlab.com/releases/2025/03/12/patch-release-g...
Um texto relacionado é o artigo da Latacora de 2019, How (not) to sign a JSON object[1]
Em resumo, assinar estruturas em árvore aninhadas é difícil e cheio de armadilhas
É mais fácil que o envelope contenha a mensagem como uma string bruta e assinar essa string bruta
[1]: https://www.latacora.com/blog/2019/07/24/how-not-to/
A conclusão mais simples aqui não seria procurar a assinatura no lugar onde ela deve estar?
Não use um XPath genérico demais, como “//ds:Signature”, a ponto de acabar encontrando qualquer assinatura em um lugar inesperado
Não basta remover cirurgicamente apenas o componente perigoso; é preciso jogar fora um pouco do bebê junto com a água do banho, cortar em massa e tratar como quimioterapia
Qualquer administrador de TI que tenha orgulho do que faz deveria excluir SAML dos planos futuros
A ideia inteira de SSO também é suspeita, e parsing de XML já apanhou duas vezes nesta semana, então deve ser evitado daqui para frente
O que há de errado em uma política de substituir XML por JSON?
Isso vale desde o design, os protocolos e os formatos de dados
Os hábitos e as considerações de design do desenvolvimento web comum não combinam com o que código de segurança exige e, muitas vezes, são exatamente o oposto do necessário para escrever código correto
É meio irritante que o post do blog explique a vulnerabilidade mas deixe de fora justamente a diferença entre parsers que causou o problema
É como escrever a introdução da história e omitir o clímax
Ao fazer
(...//ds:DigestValue).firstChild.nodeValue, não verificaram se.firstChildera um Node; no caso problemático, era um CommentAssim, o lado não normalizado via a assinatura “oculta”, enquanto o lado modificado, que descartava comentários, via um Node; quando duas implementações chegam a conclusões diferentes sobre um documento assinado, coisas nada engraçadas acontecem
Os detalhes concretos devem aparecer em breve