Contornando a Autenticação SSO SAML com Diferenças de Parser

Você já imaginou um cenário onde alguém pudesse se passar por qualquer usuário em um sistema? Recentemente, pesquisadores descobriram falhas críticas no ruby-saml, uma biblioteca usada para autenticação Single Sign-On (SSO). Essas vulnerabilidades permitem que um invasor, com apenas uma assinatura válida, construa suas próprias declarações SAML e, assim, acesse contas de qualquer usuário. A boa notícia é que a versão 1.18.0 já está disponível para corrigir esses problemas, então, bora atualizar!

O que rolou nos bastidores da descoberta?

Descrição da imagem

A história começa com o ruby-saml, uma biblioteca que facilita o SSO via SAML. Embora o GitHub não utilize essa biblioteca para autenticação no momento, eles estavam avaliando sua implementação. A equipe de segurança do GitHub, junto com pesquisadores do programa de bug bounty, identificou vulnerabilidades exploráveis que poderiam levar ao bypass de autenticação SAML. Isso aconteceu após a descoberta de uma vulnerabilidade anterior (CVE-2024-45409) por ahacker1.

O que tornou essa vulnerabilidade tão interessante foi o uso de dois parsers XML diferentes: REXML e Nokogiri. Essa diferença de interpretação abriu brechas para que um atacante pudesse enganar o sistema, verificando a assinatura errada e obtendo acesso indevido. A equipe do GitHub Security Lab e os pesquisadores de bug bounty trabalharam juntos para investigar e encontrar uma solução.

A vulnerabilidade foi explorada devido a uma **diferença na forma como os parsers XML interpretavam o mesmo documento**. Essa não é a primeira vez que diferenças entre parsers causam problemas de segurança. Ivan Fratric, por exemplo, já demonstrou como explorar essas diferenças para realizar ataques.

Para confirmar o impacto da vulnerabilidade, foi necessário criar um exploit completo de bypass de autenticação. Esse processo envolveu descobrir que dois parsers estavam em uso, entender como a diferença entre eles poderia ser explorada, encontrar uma diferença real e, finalmente, criar um exploit funcional.

Como funciona a validação de respostas SAML?

As respostas SAML são usadas para transportar informações sobre um usuário autenticado do provedor de identidade (IdP) para o provedor de serviço (SP) em formato XML. Normalmente, a informação mais importante é o nome de usuário ou endereço de e-mail. Quando o HTTP POST binding é usado, a resposta SAML passa pelo navegador do usuário final, tornando essencial a verificação da assinatura para evitar adulterações.

Em uma resposta SAML simplificada, a parte principal é o elemento Assertion, que contém informações sobre o usuário, como o NameID (nome de usuário). A Assertion é canonicalizada e comparada com o DigestValue, enquanto o SignedInfo é canonicalizado e verificado contra o SignatureValue. A Assertion ou toda a resposta SAML pode ser assinada.

Para entender melhor, imagine a Assertion como um documento de identidade digital. Esse documento contém informações sobre você (nome, e-mail, etc.) e é assinado para garantir que não foi falsificado. O processo de validação SAML garante que essa assinatura é válida e que as informações no documento são confiáveis.

A caça pelas diferenças entre os parsers

O ruby-saml usava dois parsers XML diferentes, REXML e Nokogiri, para validar a resposta SAML. O método validate_signature no arquivo xml_security.rb é onde a mágica (ou o caos) acontecia. Dentro desse método, uma consulta XPath com REXML busca o primeiro elemento Signature no documento SAML.

Para diferenciar as consultas entre REXML e Nokogiri, basta observar a forma como são chamadas: os métodos REXML são prefixados com REXML::, enquanto os métodos Nokogiri são chamados diretamente no documento. O SignatureValue é então lido desse elemento.

Um elemento Signature real contém o SignedInfo, que inclui o DigestValue (o hash da Assertion) e informações sobre a chave usada. Mais tarde, o código consulta novamente por Signature, mas desta vez com Nokogiri. O elemento SignedInfo é retirado dessa assinatura e canonicalizado.

Em seguida, o elemento SignedInfo também é extraído com REXML. A partir desse elemento, o nó Reference é lido. O código então consulta o nó referenciado, procurando por nós com o ID do elemento assinado usando Nokogiri. Um dos nós de referência é pego e canonicalizado. O canon_hashed_element é então hashed.

O DigestValue para comparação é extraído com REXML. Finalmente, o hash (construído a partir do elemento extraído por Nokogiri) é comparado com o digest_value (extraído com REXML). A canon_string extraída anteriormente (resultado de uma extração com Nokogiri) é verificada contra a signature (extraída com REXML).

No final, temos a seguinte situação:

  1. A Assertion é extraída e canonicalizada com Nokogiri, e então hashed. Em contraste, o hash contra o qual será comparado é extraído com REXML.
  2. O elemento SignedInfo é extraído e canonicalizado com Nokogiri – ele é então verificado contra o SignatureValue, que foi extraído com REXML.

Explorando as diferenças entre os parsers

A grande questão era: seria possível criar um documento XML onde REXML visse uma assinatura e Nokogiri visse outra? A resposta é sim. Ahacker1, inspirado pelas vulnerabilidades de XML roundtrips publicadas por Juho Forsén da Mattermost, foi o primeiro a produzir um exploit funcional usando essa diferença entre os parsers.

Logo depois, outro pesquisador produziu um exploit usando uma diferença diferente com a ajuda do fuzzer Ruby da Trail of Bits, chamado ruzzy. Ambos os exploits resultam em um bypass de autenticação. Isso significa que um atacante, com uma única assinatura válida criada com a chave usada para validar as respostas SAML, pode construir declarações para qualquer usuário que serão aceitas pelo ruby-saml.

Essa assinatura pode vir de uma declaração assinada ou resposta de outro usuário (não privilegiado) ou, em certos casos, até mesmo de metadados assinados de um provedor de identidade SAML (que podem ser acessíveis publicamente).

Em resumo, o elemento SignedInfo da assinatura visível para Nokogiri é canonicalizado e verificado contra o SignatureValue extraído da assinatura vista por REXML. A Assertion é recuperada via Nokogiri procurando por seu ID, canonicalizada e hashed. O hash é comparado ao DigestValue, que foi recuperado via REXML. Este DigestValue não tem uma assinatura correspondente.

Com isso, duas coisas acontecem:

  • Um SignedInfo válido com DigestValue é verificado contra uma assinatura válida (o que é correto).
  • Uma Assertion canonicalizada fabricada é comparada contra seu digest calculado (o que também é correto).

Isso permite que um atacante, com uma Assertion assinada válida para qualquer usuário (não privilegiado), fabrique Assertions e, assim, se passe por qualquer outro usuário. Para se proteger, é importante verificar se há erros ao usar Nokogiri nas respostas SAML.

Partes dos exploits atualmente conhecidos podem ser interrompidas verificando se há erros de parsing do Nokogiri nas respostas SAML. Infelizmente, esses erros não resultam em exceções, mas precisam ser verificados no membro errors do documento analisado. Embora essa não seja uma correção perfeita, ela impede pelo menos um exploit.

O que fazer agora?

Se você usa a biblioteca ruby-saml, atualize para a versão 1.18.0, que contém correções para as vulnerabilidades CVE-2025-25291 e CVE-2025-25292. As referências a bibliotecas que usam ruby-saml (como omniauth-saml) também precisam ser atualizadas para uma versão que referencie uma versão corrigida do ruby-saml. Fique de olho, pois o GitHub Security Lab publicará uma prova de conceito do exploit em breve.

O problema fundamental era a desconexão entre a verificação do hash e a verificação da assinatura, que era explorável por meio de diferenças de parser. A remoção de um dos parsers XML já estava planejada por outros motivos e provavelmente virá como parte de uma grande atualização, juntamente com melhorias adicionais para fortalecer a biblioteca. Se sua empresa depende de software de código aberto para funcionalidades críticas, considere patrociná-los para ajudar a financiar seu desenvolvimento futuro e lançamentos de correção de bugs.

É crucial estar atento a logins suspeitos via SAML no provedor de serviços, especialmente de endereços IP que não correspondem à localização esperada do usuário. Essa medida pode ajudar a identificar atividades maliciosas e proteger seus sistemas contra acessos não autorizados.

Integrar sistemas com SAML pode ser desafiador, mas escrever implementações de SAML usando assinaturas XML de forma segura é ainda mais complicado. Confiar em dois parsers diferentes em um contexto de segurança pode ser arriscado e propenso a erros. No entanto, a explorabilidade não é automaticamente garantida nesses casos. Verificar se há erros do Nokogiri não impede a diferença do parser, mas pode interromper pelo menos uma exploração prática dela.

A lição que tiramos disso é que, ao lidar com validação de assinaturas XML, é essencial garantir que o hash e a assinatura estejam diretamente conectados ao conteúdo verificado. A biblioteca deve usar o conteúdo do SignedInfo já extraído para obter o valor do digest, o que teria evitado a vulnerabilidade, mesmo com dois parsers XML em uso. Ou, alternativamente, usar um padrão menos complicado para transportar um nome de usuário assinado criptograficamente entre dois sistemas.

Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.

Via The GitHub Blog

Leave a Comment