AuraInspector: Salesforce Aura の監査によるデータ漏洩の検出
Mandiant
はじめに
Mandiant が、新たなオープンソース ツールである AuraInspector をリリースします。Salesforce Aura フレームワーク内のアクセス制御の構成ミスの特定と監査において防御者を支援できるよう設計されました。
Salesforce Experience Cloud は多くの企業が基盤として使用するプラットフォームです。しかし、Mandiant Offensive Security Services(OSS)によって、クレジット カード番号、身分証明書、医療情報などの機密データに不正ユーザーがアクセスできてしまうような構成ミスが頻繁に特定されています。このようなアクセス制御の不備は、手遅れになるまで気づかれないこともしばしばです。
この投稿では、こうした一般的な構成ミスについて詳しく説明します。また、これまで文書化されていなかった、GraphQL を使用して標準のレコード取得制限を回避する手法をご紹介します。環境のセキュリティを保護する管理者を支援するために、漏洩の検出を自動化し、修復のための実用的なインサイトを提供するコマンドライン ツール、AuraInspector をリリースします。
Aura とは
Aura は、再利用可能なモジュール型コンポーネントを作成するために Salesforce アプリケーションで使用されるフレームワークです。Lightning Experience と呼ばれる Salesforce の最新の UI の基盤となっています。Aura では、よりモダンでレスポンシブなシングルページ アプリケーション(SPA)モデルが導入されており、よりよいユーザー エクスペリエンスを実現できます。
Aura のセキュリティ上の主要な課題は、他のオブジェクト リレーショナル データベースやデベロッパー フレームワークと同じく、ユーザーがアクセスできるデータを、閲覧権限を持つデータのみに確実に制限することです。具体的に説明すると、フロントエンドがデータベースに保存されたオブジェクト レコードを含むさまざまな情報をバックエンド システムから取得する際に、Aura エンドポイントが使用されます。通常、エンドポイントは、Experience Cloud アプリケーションを操作してネットワーク リクエストを調べることで特定可能です。
しかし、Salesforce オブジェクトの共有ルールは複数のレベルで構成でき、それによって潜在的な構成ミスの特定が複雑になっていることが、Salesforce 管理者にとっての重要課題となってきました。Aura エンドポイントが、Salesforce Experience Cloud アプリケーションで最も標的とされやすいエンドポイントの一つであるのもこのためです。
Aura エンドポイントの重要な点は、認証されたコンテキストの権限に応じて、Aura 対応のメソッドを呼び出せることです。このメソッドの呼び出しには、Aura エンドポイントの message パラメータを使用できます。特に注目すべきは getConfigData メソッドです。このメソッドは、バックエンドの Salesforce データベースで使用されるオブジェクトのリストを返します。このメソッドを呼び出すために使用される構文は次のとおりです。
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.hostConfig.HostConfigController/ACTION$getConfigData","callingDescriptor":"UNKNOWN","params":{}}]}レスポンスの例を図 1 に示します。


図 1: getConfigData レスポンスの一部
Aura を使用してデータを取得する方法
Aura を使用したデータ取得
Salesforce Experience Cloud アプリケーションの一部のコンポーネントは、特定の Aura メソッドを暗黙的に呼び出してレコードを取得し、ユーザー インターフェースにデータを入力します。
serviceComponent://ui.force.components.controllers.
lists.selectableListDataProvider.SelectableListDataProviderController/
ACTION$getItems
Aura メソッドはこれに該当します。こうした Aura メソッドは正当なものであり、それ自体がセキュリティ リスクをもたらすことはありません。リスクは、基盤となる権限の構成ミスが原因で生じます。
Mandiant は、制御されたテスト インスタンスの中でアクセス制御に意図的に構成ミスを含め、ゲストユーザー(認証されていないユーザー)にアカウント オブジェクトのすべてのレコードへのアクセスを許可しました。これは、実際の環境でよく見られる構成ミスです。アプリケーションは通常、Aura フレームワークまたは Lightning フレームワークを使用してオブジェクト レコードを取得します。メソッドの一つでは getItems が使用されます。このメソッドを特定のパラメータとともに使用することで、ユーザーがアクセスできる特定のオブジェクトのレコードを取得できます。このメソッドを使用したリクエストとレスポンスの例を図 2 に示します。


図 2: アカウント オブジェクトのレコードを取得する
しかし、この一般的なアプローチには制約があります。Salesforce で一度に取得できるレコード数は最大 2,000 件です。数千件のレコードが含まれるオブジェクトの場合、このアプローチでは、取得できるレコード数が制限を受けます。構成ミスがもたらす影響を完全に明らかにするには、この制限の克服がしばしば必要となります。
テストにより、sortBy パラメータをこのメソッドで利用できることが判明しました。このパラメータの便利な点は、並べ替え順の変更によって、元は 2,000 件のレコード制限のためにアクセスできなかったレコードを追加で取得できることです。さらに、フィールド名の前に「-」を追加することで、任意のパラメータについて昇順または降順に並べ替えたデータを取得できます。以下は、sortBy パラメータを利用する Aura メッセージの例です。
{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"FUZZ","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":false,"enableRowActions":false,"sortBy":"<ArbitraryField>"}}]}Name フィールドが降順で並べ替えられたレスポンスを図 3 に示します。


図 3: 結果を並べ替えてアカウント オブジェクトのレコードをさらに取得する
組み込みの Salesforce オブジェクトには、デフォルトで利用できるフィールドがいくつかあります。カスタム オブジェクトの場合、カスタム フィールドに加えて、CreatedBy や LastModifiedBy などのデフォルト フィールドがいくつかあり、フィルタを適用できます。さまざまなフィールドをフィルタすることで、取得できるレコード数が大幅に増加し、セキュリティ研究者が Salesforce 管理者に潜在的な影響を示すうえで役に立ちます。
アクションの一括処理
パフォーマンスを最適化し、ネットワーク トラフィックを最小限に抑えるために、Salesforce Aura フレームワークでは「ボックスカーリング(Boxcar'ing)」と呼ばれる仕組みが採用されています。ユーザーがサーバーサイド アクションを開始すると、各アクションについて個別の HTTP リクエストが送信されるのではなく、クライアントサイドでキューに追加されます。そしてイベントループの最後で、キューに登録された複数の Aura アクションが 1 つのリストにバンドルされ、単一の POST リクエストの一部としてサーバーに送信されます。
この手法を使用しない場合、レコードとオブジェクトの数によっては、レコードの取得に大量のリクエストが必要になることがあります。その点、Salesforce ではこの手法を使用することで、1 回のリクエストにつき最大 250 件のアクションを同時に実行できます。ただし、アクションを送信しすぎると、Content-Length レスポンスが直ちに返され、リクエストが成功しない可能性があります。そのため、Mandiant はリクエスト 1 回あたりのアクションを 100 件に制限することを推奨しています。次の例では、UserFavorite オブジェクトと ProcessInstanceNode オブジェクトの両方のレコードを取得するために、2 つのアクションが一括処理されています。
{"actions":[{"id":"UserFavorite","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"UserFavorite","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":true,"enableRowActions":false}},{"id":"ProcessInstanceNode","descriptor":"serviceComponent://ui.force.components.controllers.lists.selectableListDataProvider.SelectableListDataProviderController/ACTION$getItems","callingDescriptor":"UNKNOWN","params":{"entityNameOrId":"ProcessInstanceNode","layoutType":"FULL","pageSize":100,"currentPage":0,"useTimeout":false,"getCount":true,"enableRowActions":false}}]}手動で多くの操作を行うのは大変です。AuraInspector ツールに組み込まれたこの機能により、構成ミスのあるオブジェクトを迅速に特定できます。
レコードリスト
あまり知られていないコンポーネントに、Salesforce のレコードリストがあります。このコンポーネントは、名前のとおり、ユーザーがアクセスできるオブジェクトに関連付けられたレコードのリストをユーザー インターフェースに表示するものです。レコードリストに表示できるレコードには、オブジェクトのアクセス制御が適用されますが、アクセス制御の構成ミスが原因でユーザーがオブジェクトのレコードリストにアクセスできるようになる場合があります。
Using the ui.force.components.controllers.lists.
listViewPickerDataProvider.ListViewPickerDataProviderController/
ACTION$getInitialListViews
Aura メソッドを使用して、オブジェクトにレコードリスト コンポーネントが関連付けられているかどうかを確認できます。Aura メッセージは次のようになります。
{"actions":[{"id":"1086;a","descriptor":"serviceComponent://ui.force.components.controllers.lists.listViewPickerDataProvider.ListViewPickerDataProviderController/ACTION$getInitialListViews","callingDescriptor":"UNKNOWN","params":{"scope":"FUZZ","maxMruResults":10,"maxAllResults":20},"storable":true}]}図 4 に示すようにリストビューの配列がレスポンスに含まれている場合は、レコードリストが存在する可能性があります。


図 4: getInitialListViews メソッドのレスポンスの一部
このレスポンスは、このオブジェクトに関連付けられたレコードリスト コンポーネントが存在すること、およびアクセスできる可能性があることを意味しています。アクセスが許可されている場合、/s/recordlist/<object>/Default に移動するだけでレコードのリストが表示されます。レコードリストの例を図 5 に示します。インターフェースには、既存のレコードを作成または変更する機能も用意されている場合があります。


図 5: Account オブジェクトのデフォルトのレコードリスト ビュー
ホーム URL
ホーム URL は、直接ブラウジングできる URL です。Mandiant の研究者がホーム URL にアクセスした際、Salesforce インスタンスにインストールされているサードパーティ モジュールの管理パネルや構成パネルが表示されることが何度かありました。認証済みユーザーは、以下に示すように ui.communities.components.aura.components.communitySetup.cmc.CMCAppController/ACTION$getAppBootstrapData Aura メソッドを使用してホーム URL を取得できます。
{"actions":[{"id":"1086;a","descriptor":"serviceComponent://ui.communities.components.aura.components.communitySetup.cmc.CMCAppController/ACTION$getAppBootstrapData","callingDescriptor":"UNKNOWN","params":{}}]}返された JSON レスポンスでは、apiNameToObjectHomeUrls という名前のオブジェクトに URL のリストが含まれています。次に、各 URL をブラウジングし、アクセスを検証して、コンテンツにアクセスできるようにすべきかどうかを評価します。簡単なプロセスですが、重要な発見につながる可能性があります。使用例を図 6 に示します。


図 6: レスポンスで返されたホーム URL のリスト
以前の調査案件で、Mandiant はこのメソッドを使用し、未認証ユーザーがアクセスできる Spark インスタンス管理ダッシュボードを特定しました。図 7 に示すように、ダッシュボードには管理機能が用意されていました。


図 7: Spark インスタンス管理ダッシュボード
この手法を使用することで、Salesforce 管理者は、未認証ユーザーや権限の低いユーザーがアクセスできないようにする必要のあるページを特定できます。Marketplace アプリケーションをインストールした場合、一部のページが自動作成されるため、こうしたページを手動で追跡しようとすると煩雑になりがちです。
自己登録
ここ数年、Salesforce はゲスト アカウントのデフォルトのセキュリティを強化してきました。これにより、認証済みアカウントの価値はさらに高まりました。未認証ユーザーがアクセスできないレコードへのアクセス権を付与できる可能性があるためです。インスタンスへの認証済みアクセスを防ぐソリューションの一つは、自己登録の防止です。インスタンスの設定を変更することで、自己登録を簡単に無効にできます。しかし、Mandiant は、自己登録ページへのリンクはログインページから削除されているものの、自己登録自体は無効になっていないケースを観測しました。Salesforce はこの問題が解決されたことを確認しています。
自己登録のステータスと URL を公開する Aura メソッドは、攻撃者の観点からすると非常に価値があります。LoginFormController コントローラの getIsSelfRegistrationEnabled メソッドと getSelfRegistrationUrl メソッドを次のように使用して、この情報を取得できます。
{"actions":[{"id":"1","descriptor":"apex://applauncher.LoginFormController/ACTION$getIsSelfRegistrationEnabled","callingDescriptor":"UHNKNOWN"},{"id":"2","descriptor":"apex://applauncher.LoginFormController/ACTION$getSelfRegistrationUrl","callingDescriptor":"UHNKNOWN"}]}2 つのメソッドを一括処理することで、サーバーから 2 つのレスポンスが返されます。図 8 では、最初のレスポンスで自己登録が可能であることが示され、2 番目のレスポンスで URL が返されています。


図 8: 自己登録が有効な場合のレスポンス
これにより、自己登録ページを特定するために総当たりチェックを実行する必要がなくなり、1 回のリクエストで十分になります。AuraInspector ツールは、自己登録が有効かどうかを確認し、研究者にアラートを送信します。目標は、Salesforce 管理者が、自己登録が有効になっているかどうかを外部の視点から判断できるようにすることです。
GraphQL: 2,000 件のレコード数制限を超える方法
Salesforce が提供する GraphQL API を使用すると、Salesforce インスタンスから User Interface API を介してアクセスできるオブジェクトのレコードを簡単に取得できます。GraphQL API 自体は Salesforce によって十分に文書化されています。しかし、GraphQL Aura コントローラに関連する公式のドキュメントや調査はありません。


図 9: ドキュメントの GraphQL クエリ
ドキュメントが不足していても、使用できないわけではありません。Mandiant は、REST API ドキュメントを確認したうえで、GraphQL Aura コントローラの情報を取得するための有効なリクエストを作成しました。さらに、このコントローラは未認証ユーザーもデフォルトで利用できました。既知のメソッドの代わりに GraphQL を使用するメリットは多くあります。
-
オブジェクトに関するレコードと情報の取得の標準化
-
オブジェクトに関連付けられたすべてのレコードを取得できるように改善されたページネーション
-
フィールド名の取得を容易にする組み込みのイントロスペクション
-
オブジェクトに対する書き込み権限の迅速なテストを可能にするミューテーションのサポート
データの取得という観点から見た主な利点は、2,000 件のレコード数制限を受けることなく、オブジェクトに関連付けられたすべてのレコードを取得できることです。Salesforce によって、これが脆弱性につながらないことが確認されています。GraphQL は基盤となるオブジェクトの権限を尊重し、オブジェクトへのアクセス権が適切に構成されている限り、追加のアクセス権を付与することはありません。しかし、構成ミスがあった場合、攻撃者は構成ミスのあるオブジェクトのレコードを好きなだけ取得できてしまいます。基本的な Aura コントローラを使用する場合、2,000 件を超えるレコードを取得するには、並べ替えフィルタを使用するしかありません。ただし、この方法では常に一貫した結果が得られるとは限りません。一方、GraphQL コントローラを使用すると、レコードを常に最大限まで取得できます。2,000 件を超えるレコードを取得するその他の方法は SOAP API と REST API ですが、権限のないユーザーがアクセスできることはほとんどありません。
GraphQL コントローラには、User Interface API(UIAPI)でサポートされているオブジェクトのレコードしか取得できないという制約があります。関連する Salesforce GraphQL API ドキュメントで説明されているように、「User Interface API は、すべてのカスタム オブジェクトと外部オブジェクト、および多くの標準オブジェクトをサポートしている」ため、ほとんどのオブジェクトは網羅されています。
GraphQL Aura コントローラ自体のドキュメントはないため、API ドキュメントを参照として使用しました。API ドキュメントには、GraphQL API エンドポイントを操作する次の例が記載されています。
curl "https://{MyDomainName}[.my.salesforce.com/services/data/v64.0/graphql](https://.my.salesforce.com/services/data/v64.0/graphql)" \
-X POST \
-H "content-type: application/json" \
-d '{
"query": "query accounts { uiapi { query { Account { edges { node { Name { value } } } } } } }"
}この例は、GraphQL Aura コントローラに転置され、次の Aura メッセージが機能することが判明しました。
{"actions":[{"id":"GraphQL","descriptor":"aura://RecordUiController/ACTION$executeGraphQL","callingDescriptor":"markup://forceCommunity:richText","params":{"queryInput":{"operationName":"accounts","query":"query+accounts+{uiapi+{query+{Account+{edges+{node+{+Name+{+value+}}}totalCount,pageInfo{endCursor,hasNextPage,hasPreviousPage}}}}}","variables":{}}},"version":"64.0","storable":true}]}これにより、API アクセスを必要とせずに GraphQL API と同じ機能が提供されます。ページネーションを容易にするために、レスポンスに endCursor、hasNextPage、hasPreviousPage フィールドが追加されました。リクエストとレスポンスを図 10 に示します。


図 10: GraphQL Aura コントローラを使用した場合のレスポンス
レコードは、クエリされたフィールドと、カーソルを含む pageInfo オブジェクトとともに返されます。カーソルを使用すると、次のレコードの取得が可能です。前述の例では、わかりやすいように 1 件のレコードのみを取得しましたが、first パラメータを 2000 に設定すると、2,000 件のレコードを一括で取得できます。カーソルは図 11 のように使用できます。


図 11: カーソルを使用して次のレコードを取得する
カーソルは、取得した最新のレコードを示す Base64 エンコード文字列であるため、ゼロから簡単に作成できます。2,000 件のレコードのバッチで、2,000~4,000 件のアイテムを取得する場合のメッセージは次のようになります。
message={"actions":[{"id":"GraphQL","descriptor":"aura://RecordUiController/ACTION$executeGraphQL","callingDescriptor":"markup://forceCommunity:richText","params":{"queryInput":{"operationName":"accounts","query":"query+accounts+{uiapi+{query+{Contact(first:2000,after:\"djE6MTk5OQ==\"){edges+{node+{+Name+{+value+}}}totalCount,pageInfo{endCursor,hasNextPage,hasPreviousPage}}}}}","variables":{}}},"version":"64.0","storable":true}]}この例で、after パラメータに設定されたカーソルは、Base64 形式の v1:1999 です。1999 より後のアイテムを取得するように Salesforce に指示しています。クエリは、特定のレコードを検索するための高度なフィルタや結合オペレーションを伴う、はるかに複雑なものにすることが可能です。1 つのクエリで複数のオブジェクトを取得することもできます。また、ここでは詳しく説明しませんが、GraphQL コントローラを使用して、ミューテーション クエリでレコードを更新、作成、削除することもできます。これにより、未認証ユーザーが API アクセスを必要とせずに、複雑なクエリやオペレーションを実行できます。
修復
このブログ投稿で説明した課題はすべて、オブジェクトとフィールドの構成ミスに起因しています。Salesforce 管理者は、大まかに次の手順でこうした問題を修復する必要があります。
-
ゲストユーザーの権限を監査する: 認証されていないゲストユーザー プロファイルを定期的に見直し、最小権限の原則を適用します。ゲストユーザーのオブジェクト セキュリティについて、Salesforce のセキュリティに関するベスト プラクティスに沿って対応します。一般公開機能に必要な特定のオブジェクトとフィールドに対する読み取りアクセス権のみを付与してください。
-
認証済みユーザーの非公開データを保護する: 共有ルールと組織全体のデフォルト設定を確認して、認証済みユーザーが明示的にアクセス権を付与されたレコードとオブジェクトにのみアクセスできるようにします。
-
自己登録を無効にする: 必要がない場合は、自己登録機能を無効にして、不正なアカウント作成を防ぎます。
-
Salesforce のセキュリティに関するベスト プラクティスに即す: Salesforce が提供するセキュリティに関する推奨事項を実装します。Security Health Check ツールの使用も含まれます。
Salesforce は、オブジェクトの共有ルール、フィールド セキュリティ、ロギング、リアルタイム イベント モニタリングなどを適切に構成する方法を詳しく説明した、包括的なセキュリティ ガイドを提供しています。
オールインワン ツール: AuraInspector
構成ミスの発見を支援するため、Mandiant は AuraInspector をリリースします。このツールは、この投稿で説明された手法を自動化し、潜在的な問題点を特定するのに役立ちます。Mandiant は、レコードを抽出する機能を備えた内部バージョンも開発していますが、不正使用を避けるため、一般公開版にはデータ抽出機能が実装されていません。このツールのオプションと機能を図 12 に示します。


図 12: AuraInspector ツールのヘルプ メッセージ
また、AuraInspector ツールは、次のような重要なコンテキスト情報の自動検出も試みます。
-
Aura エンドポイント: 追加テストのために Aura エンドポイントを自動的に特定します。
-
ホームとレコードリストの URL: ホームページとレコードリストへのダイレクト URL を取得し、ユーザーのナビゲーション パスとアクセス可能なデータビューに関するインサイトを提供します。
-
自己登録のステータス: 自己登録が有効になっているかどうかを判断し、有効になっている場合は自己登録 URL を提供します。
このツールによって実行されるすべてのオペレーションは、データの読み取りに厳密に制限されています。対象の Salesforce インスタンスが影響を受けたり変更されたりすることはありません。AuraInspector は今すぐダウンロードできます。
Salesforce インスタンスの検出
Salesforce Experience Cloud アプリケーションは、Aura エンドポイントに明確なリクエストを行うことが多いものの、アプリケーションの統合がわかりづらいこともあります。Mandiant は、Salesforce Experience Cloud アプリケーションへの参照が大規模な JavaScript ファイルに埋め込まれているのを頻繁に確認しています。次のような Salesforce ドメインへの参照を探すことをおすすめします。
-
*.vf.force.com -
*.my.salesforce-sites.com -
*.my.salesforce.com
以下は、隠れた参照を特定するのに役立つ簡単な Burp Suite BCheck です。
metadata:
language: v2-beta
name: "Hidden Salesforce app detected"
description: "Salesforce app might be used by some functionality of the application"
tags: "passive"
author: "Mandiant"
given response then
if ".my.site.com" in {latest.response} or ".vf.force.com" in {latest.response} or ".my.salesforce-sites.com" in {latest.response} or ".my.salesforce.com" in {latest.response} then
report issue:
severity: info
confidence: certain
detail: "Backend Salesforce app detected"
remediation: "Validate whether the app belongs to the org and check for potential misconfigurations"
end ifこれは基本的なテンプレートです。他の関連するパターンを使用して、Salesforce インスタンスをより適切に特定できるように調整できます。
以下は、潜在的な Salesforce インスタンスの Aura エンドポイントへの POST リクエストに関連する、Google SecOps のイベントを特定するのに役立つ代表的な UDM クエリです。
target.url = /\/aura$/ AND
network.http.response_code = 200 AND
network.http.method = "POST"これは基本的な UDM クエリです。他の関連するパターンを使用して、Salesforce インスタンスをより適切に特定できるように調整できます。
Mandiant のサービス
Mandiant Consulting は、組織の Salesforce 環境の監査と、堅牢なアクセス制御の実装を支援します。Google のエキスパートが、構成ミスの特定、セキュリティ ポスチャーの検証、ベスト プラクティスに沿ったコンプライアンスの確保を通じて機密データを保護するお手伝いをします。
謝辞
今回の分析は、Mandiant Offensive Security Services(OSS)チームの皆様の協力がなければ実現しえませんでした。また、Salesforce の皆様のご協力と包括的なドキュメントのご提供にも感謝いたします。
- Mandiant
