AuraInspector:データ露出を目的としたSalesforce Auraの監査

執筆者:Amine Ismail、Anirudha Kanodia


はじめに 

Mandiantは、Salesforce Auraフレームワーク内のアクセス制御の誤設定を防御側が特定・監査できるよう支援するために設計された、新しいオープンソースツール「AuraInspector」を公開します。

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アプリケーションにおいて最も頻繁に狙われるエンドポイントの1つとなっています。

Auraエンドポイントの最も興味深い点は、認証コンテキストの権限に応じて、aura-enabledメソッドを呼び出せることです。このエンドポイントのmessageパラメータを使用して、これらのメソッドを呼び出せます。特に注目すべきは、バックエンドのSalesforceデータベースで使用されるオブジェクトの一覧を返すgetConfigDataメソッドです。以下は、この特定メソッドを呼び出すための構文です。

{"actions":[{"id":"123;a","descriptor":"serviceComponent://ui.force.components.controllers.hostConfig.HostConfigController/ACTION$getConfigData","callingDescriptor":"UNKNOWN","params":{}}]}

応答例を図1に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig1.max-1500x1500.png

図1:getConfigData応答の抜粋

Auraを用いたデータ取得方法

Auraによるデータ取得

Salesforce Experience Cloudアプリケーション内の特定のコンポーネントは、ユーザーインターフェースを埋めるためにレコードを取得する目的で、暗黙的に特定のAuraメソッドを呼び出します。これはserviceComponent://ui.force.components.controllers.
lists.selectableListDataProvider.SelectableListDataProviderController/
ACTION$getItems
Auraメソッドが該当します。これらのAuraメソッド自体は正当であり、それ単体ではセキュリティリスクにはなりません。リスクは、基盤となる権限設定が誤っている場合に生じます。

制御されたテストインスタンスにおいて、Mandiantはアクセス制御を意図的に誤設定し、ゲスト(未認証)ユーザーにAccountオブジェクトの全レコードへのアクセスを付与しました。これは実際のエンゲージメントでもよく遭遇する誤設定です。通常、アプリケーションはAuraまたはLightningフレームワークを用いてオブジェクトレコードを取得します。その方法の1つがgetItemsです。このメソッドを特定のパラメータで使用すると、ユーザーがアクセスできる特定オブジェクトのレコードを取得できます。このメソッドを用いたリクエストとレスポンスの例を図2に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig2.max-1100x1100.png

図2:Accountオブジェクトのレコード取得

しかし、この一般的なアプローチには制約があります。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に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig3.max-1000x1000.png

図3:結果をソートしてAccountオブジェクトの追加レコードを取得

Salesforceの標準オブジェクトには、デフォルトで利用可能なフィールドが複数あります。カスタムオブジェクトでは、カスタムフィールドに加えて、CreatedByLastModifiedByなど、フィルタに利用できるデフォルトフィールドがいくつか存在します。さまざまなフィールドでフィルタリングすることで、より多くのレコードを取得しやすくなります。より多くのレコードを取得できることは、Salesforce管理者に対して潜在的な影響を示すうえで、セキュリティ研究者にとって有用です。

アクションのバルク化

パフォーマンスを最適化し、ネットワークトラフィックを最小化するために、Salesforce Auraフレームワークは「boxcar’ing」と呼ばれる仕組みを採用しています。ユーザーが開始する個々のサーバーサイドアクションごとに別々のHTTPリクエストを送る代わりに、フレームワークはクライアント側でこれらのアクションをキューに入れます。イベントループの終わりに、キューに入った複数のAuraアクションを単一のリストにまとめ、それを単一の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のRecord Lists(レコードリスト)があります。このコンポーネントは、その名のとおり、ユーザーがアクセスできるオブジェクトに関連付けられたレコードの一覧をユーザーインターフェース上に提供します。オブジェクトに対するアクセス制御は引き続きRecord Listで閲覧できるレコードを制御しますが、アクセス制御の誤設定により、ユーザーがあるオブジェクトのRecord Listにアクセスできてしまう可能性があります。

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に示すように、応答にリストビューの配列が含まれていれば、Record Listが存在する可能性が高いといえます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig4.max-1000x1000.png

図4:getInitialListViewsメソッドの応答抜粋

この応答は、このオブジェクトに関連付けられたRecord Listコンポーネントが存在し、アクセス可能である可能性があることを意味します。単に/s/recordlist/<object>/Defaultへ移動するだけで、アクセスが許可されていればレコード一覧が表示されます。Record Listの例を図5に示します。インターフェースによっては、既存レコードの作成や変更が可能な場合もあります。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig5.max-900x900.png

図5:AccountオブジェクトのデフォルトRecord Listビュー

ホームURL

ホームURLとは、直接ブラウズできるURLのことです。複数回にわたり、これらのURLを辿ることで、Mandiantの研究者はSalesforceインスタンスにインストールされたサードパーティモジュールの管理パネルや設定パネルに到達しました。これらは、認証済みユーザーがui.communities.components.aura.components.communitySetup.cmc.
CMCAppController/ACTION$getAppBootstrapData
Auraメソッドを用いて次のように取得できます。

{"actions":[{"id":"1086;a","descriptor":"serviceComponent://ui.communities.components.aura.components.communitySetup.cmc.CMCAppController/ACTION$getAppBootstrapData","callingDescriptor":"UNKNOWN","params":{}}]}

返却されるJSON応答では、apiNameToObjectHomeUrlsという名前のオブジェクトにURL一覧が含まれます。次のステップは、各URLをブラウズしてアクセス可否を確認し、その内容がアクセス可能であるべきかどうかを評価することです。これは単純なプロセスですが、興味深い発見につながることがあります。利用例を図6に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig6.max-800x800.png

図6:応答で返されたホームURL一覧

過去のエンゲージメントでは、Mandiantはこの方法により、未認証ユーザーであってもアクセス可能なSparkインスタンスの管理ダッシュボードを特定しました。ダッシュボードには、図7に示すような管理機能が提供されていました。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig7.max-900x900.png

図7:Sparkインスタンス管理ダッシュボード

この手法により、Salesforce管理者は未認証ユーザーや低権限ユーザーがアクセスできるべきではないページを特定できます。マーケットプレイスアプリケーションのインストール時に自動生成されるページもあるため、これらのページを手作業で追跡するのは煩雑になりがちです。

セルフ登録

ここ数年で、Salesforceはゲストアカウントのデフォルトセキュリティを強化してきました。そのため、認証済みアカウントを持つことは、未認証ユーザーにはアクセスできないレコードへのアクセスを得られる可能性があり、さらに価値が高まっています。インスタンスへの認証済みアクセスを防ぐ1つの解決策は、セルフ登録を防止することです。セルフ登録は、インスタンス設定を変更することで容易に無効化できます。しかし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が返されています。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig8.max-1000x1000.png

図8:セルフ登録が有効な場合の応答

これにより、セルフ登録ページを特定するための総当たりが不要になり、1回のリクエストで十分になります。AuraInspector ツールは、セルフ登録が有効かどうかを検証し、研究者に通知します。目的は、外部の観点からセルフ登録が有効かどうかをSalesforce管理者が判断できるよう支援することです。

GraphQL:2,000件のレコード上限を超えて

SalesforceはGraphQL APIを提供しており、SalesforceインスタンスのUser Interface API経由でアクセス可能なオブジェクトから、レコードを容易に取得できます。GraphQL API自体はSalesforceにより十分に文書化されています。しかし、GraphQL Auraコントローラに関する公式ドキュメントや研究は存在しません。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig9.max-900x900.png

図9:ドキュメントにあるGraphQLクエリ

ただし、この文書化の欠如は利用を妨げるものではありません。REST APIドキュメントを確認した後、MandiantはGraphQL Auraコントローラの情報を取得するための有効なリクエストを構築しました。さらに、このコントローラはデフォルトで未認証ユーザーから利用可能でした。既知のメソッドに比べ、GraphQLを用いることには複数の利点があります。

  • レコードおよびオブジェクト情報の標準化された取得

  • ページネーションの改善により、オブジェクトに紐づく全レコードの取得が可能

  • 組み込みのイントロスペクションにより、フィールド名の取得が容易

  • ミューテーションのサポートにより、オブジェクトに対する書き込み権限のテストを迅速化

データ取得の観点での最大の利点は、2,000件の制限に縛られず、オブジェクトに紐づく全レコードを取得できる点です。Salesforceは、これは脆弱性ではないと確認しています。GraphQLは基盤となるオブジェクト権限を尊重し、オブジェクトへのアクセスが適切に設定されている限り、追加のアクセスを提供しません。しかし誤設定がある場合、誤設定されたオブジェクト上の任意の量のレコードに攻撃者がアクセスする助けになります。基本的なAuraコントローラでレコードを取得する場合、2,000件を超えて取得する唯一の方法はソートフィルタを用いることですが、常に一貫した結果が得られるとは限りません。GraphQLコントローラを使用すると、可能な最大数のレコードを一貫して取得できます。2,000件を超えて取得する他の選択肢としてSOAPおよびREST APIがありますが、これらは非特権ユーザーからアクセスできることは稀です。

GraphQLコントローラの制約の1つは、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と同等の機能を提供できます。ページネーションを容易にするため、応答にendCursorhasNextPagehasPreviousPageフィールドを追加しました。リクエストと応答は図10に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig10.max-1200x1200.png

図10:GraphQL Auraコントローラ使用時の応答

レコードはクエリしたフィールドとともに返され、カーソルを含むpageInfoオブジェクトが含まれます。このカーソルを用いることで、次のレコードを取得できます。前述の例では可読性のため1件のみ取得しましたが、firstパラメータを2000に設定することで、2,000件単位のバッチで実行できます。その後、図11に示すようにカーソルを使用できます。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig11.max-900x900.png

図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パラメータに設定されたカーソルはv1:1999のbase64です。これはSalesforceに対して1999以降の項目を取得するよう指示します。クエリは、特定レコードを検索するための高度なフィルタリングや結合操作を含むなど、はるかに複雑にできます。また、1つのクエリで複数オブジェクトを取得することも可能です。ここでは詳細には扱いませんが、GraphQLコントローラはミューテーションクエリを用いてレコードの更新・作成・削除にも使用できます。これにより、未認証ユーザーであってもAPIアクセスを必要とせずに、複雑なクエリや操作を実行できてしまいます。

是正

本ブログで説明した問題はすべて、特にオブジェクトおよびフィールドに関する誤設定に起因します。高いレベルでは、Salesforce管理者はこれらの問題を是正するために次の手順を取るべきです。

  • ゲストユーザー権限の監査:未認証のゲストユーザープロファイルについて、定期的に見直し、最小権限の原則を適用してください。ゲストユーザーのオブジェクトセキュリティに関するSalesforceのセキュリティベストプラクティスに従ってください。公開向け機能に必要な特定のオブジェクトおよびフィールドに対してのみ読み取りアクセスを持つことを確実にしてください。

  • 認証済みユーザーの機密データを保護:共有ルールおよび組織全体のデフォルトを見直し、認証済みユーザーが明示的に付与された権限のあるレコードとオブジェクトにのみアクセスできることを確実にしてください。

  • セルフ登録を無効化:不要であれば、セルフ登録機能を無効化し、不正なアカウント作成を防止してください。

  • Salesforceのセキュリティベストプラクティスに従う:Salesforceが提供するセキュリティ推奨事項を実装してください。これにはSecurity Health Checkツールの利用も含まれます。

Salesforceは、オブジェクト共有ルール、フィールドセキュリティ、ログ、リアルタイムイベント監視などを適切に設定する方法を詳述した包括的なセキュリティガイドを提供しています。

オールインワンツール:AuraInspector

これらの誤設定の発見を支援するため、MandiantはAuraInspectorを公開します。本ツールは、本稿で説明した手法を自動化し、潜在的な不備の特定を支援します。Mandiantはレコード抽出機能を備えた社内版も開発しましたが、悪用を避けるため、公開版にはデータ抽出機能を実装していません。ツールのオプションと機能を図12に示します。

https://storage.googleapis.com/gweb-cloudblog-publish/images/aurainspector-fig12.max-800x800.png

図12:AuraInspectorツールのヘルプメッセージ

AuraInspector ツールは、次のような価値の高いコンテキスト情報の自動発見も試みます。

  • Auraエンドポイント:追加テストのためにAuraエンドポイントを自動的に特定します。

  • ホームおよびRecord ListのURL:ホームページやレコードリストへの直接URLを取得し、ユーザーのナビゲーション経路やアクセス可能なデータビューに関する洞察を提供します。

  • セルフ登録の状態:セルフ登録が有効かどうかを判定し、有効な場合はセルフ登録URLを提供します。

本ツールが実行するすべての操作はデータの読み取りに厳密に限定されており、対象のSalesforceインスタンスに影響を与えたり変更したりしないことを保証します。AuraInspectorは今すぐダウンロード可能です

Salesforceインスタンスの検出

Salesforce Experience CloudアプリケーションはAuraエンドポイントに対して明確なリクエストを行うことが多い一方で、アプリケーションの統合がより目立たない場合もあります。Mandiantは、大規模なJavaScriptファイルの中に埋もれたSalesforce Experience Cloudアプリケーションへの参照をしばしば観測します。次のような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環境を監査し、堅牢なアクセス制御を実装することを支援できます。当社の専門家は、誤設定の特定、セキュリティ態勢の検証、機微データを保護するためのベストプラクティスへの準拠の確保を支援します。

謝辞

本分析は、Mandiant Offensive Security Services(OSS)チームの支援なしには実現しませんでした。また、協力と包括的なドキュメントを提供してくれたSalesforceにも感謝します。

投稿先

翻訳元: https://cloud.google.com/blog/topics/threat-intelligence/auditing-salesforce-aura-data-exposure/

ソース: cloud.google.com