近年、クラウドサービスを利用したシステムを目にする機会が増えてきました。セキュリティ診断サービスを提供する中でもクラウドサービスを利用したシステムのセキュリティ診断依頼は増加しています。その中でもGoogleが提供するFirebaseの各種サービスに対してセキュリティ診断を行う中で、様々な脆弱性を見つけることができました。
今回から公開するブログシリーズは弊社が実施してきたFirebaseに対するセキュリティ診断で見つかった問題を題材に取り入れつつ、設計や実装時に作り込まれやすい問題点を事例を交えながら紹介していこうという試みになります。
できる限り具体例も交えた分かりやすい説明を心がけているので、Firebaseを既に利用している方だけではなく、これから利用しようとされている方々にとっても、本ブログがFirebaseを利用する際のセキュリティを考えるきっかけになれば幸いです。
Firebaseとは
FirebaseはGoogle社が提供するモバイル・Webアプリケーション開発プラットフォームであり、フルマネージドのバックエンドインフラストラクチャを提供することによる円滑なアプリケーション開発、パフォーマンスや稼働状況のモニタリング機能の提供など、アプリ開発における様々なサポートを行うプロダクトやソリューションを提供していることが大きな特徴となっています。
各サービスは以下のように関連しており、以降各サービスの概要について説明していきます。
Firebaseの各種サービス紹介
本ブログでは、Firebaseで作り込まれやすい問題を具体的な例も交えつつ説明しますが、まず導入としてFirebaseの代表的な機能・サービスの概要をまとめます。(下表の機能・サービスが弊社のFirebase診断のスコープとなっています。)
機能名 | サービス名 | 概要 |
データアクセス | Cloud Firestore | モバイルアプリやWebアプリデータの保存、同期、参照が可能であるデータベース 。 |
Realtime Database | Cloud Firestoreが登場する前から利用されてきたFirebase上で利用できるデータベース 。 | |
ファイルアクセス | Cloud Storage | ファイルの保存、管理、配信を行うためのストレージサービス。 |
処理 | Cloud Functions | Firebaseの機能などによって発生したイベントに応じてコードを実行するフレームワーク。 |
認証 | Firebase Authentication | パスワード、電話番号、一般的なフェデレーション ID プロバイダ(Google、Facebook、Twitter)などを使用した認証機能。 |
配信 | Hosting | Webアプリなど様々なコンテンツを公開することができるサービス。 |
Firebase全体としての脆弱性、セキュリティ上の問題
一口にFirebaseと言っても様々なサービスを組み合わせてアプリ開発を行います。よって、1つのサービスにおけるセキュリティ対策ではなく、それぞれの機能毎にセキュリティを考慮した上でのサービス利用が求められることになります。
例えばCloud Firestoreに着目するとセキュリティルールの問題に起因するアクセス制御の不備、Firebase Authenticationであれば認証機能における認証情報の列挙やパスワードポリシーに関する問題などが挙げられますが、今回は Firebase Authenticationについて紹介します。
Firebase Authenticationに関する脆弱性・セキュリティ上の問題について
Firebaseに関して第1弾となる本ブログでは、Firebase Authenticationについて紹介します。Firebase AuthenticationはFirebaseにおけるIDaaSに位置するプロダクトですが、その特性上適切な設定を実施しないとセキュリティ上の問題につながる危険性が高いものとなるため、発生し得る問題をいくつか紹介します。
メール/パスワード認証におけるメールアドレス列挙の問題について
まず比較的わかりやすい問題から順番に紹介していこうと思います。
Firebase Authenticationでは、様々な認証プロバイダを利用することができます。
*Firebaseコンソールで設定できるAuthenticationの認証プロバイダ一覧
このうち、OAuthやOIDCのプロバイダではなく、独自にID/パスワード情報を登録させて管理するとなると、基本的にはメール/パスワード認証が使用されることが多いのではないでしょうか。(カスタム認証用に独自認証バックエンドを作り、社内ネットワーク上のADサーバと連携させることなども可能ですが、今回そのような個別のカスタマイズに関するお話は別の機会に譲ることにします。)
メール/パスワード認証を使用している場合、Firebaseセキュリティチェックリストでは、メールの列挙保護を有効にすることを推奨しています。
メールの列挙保護をしないとどうなるのでしょう?
簡単に説明すると、ログインIDとなるメールアドレスの列挙が出来てしまいます。
一般的なWebアプリケーション診断でも、ログイン機能については同様の観点で診断することがありますが、例えばログインページで
- ログインIDは正しい(存在する)が、パスワードが違う時
- 「パスワードが違います」というエラーメッセージを表示する
- ログインIDが存在しない時
- 「指定されたログインIDは存在しません」というエラーメッセージを表示する
※説明のため、パスワードをマスクしないようにしています
という仕様になっていた場合、パスワードを適当な値にしてログインID部分を総当たりしていくと、実際に対象のWebサイトに登録している(存在する)ログインIDが判明してしまいます。パスワードは判明していないのでこれだけでは不正ログインが成功する訳ではありません。
が、Firebaseのメール/パスワード認証の場合、ログインIDに相当するのはユーザのメールアドレスです。これが判明してしまうというのは、対象のアプリケーションを利用しているユーザのメールアドレスに対してスパムやフィッシングメールが送信されるようになってしまったり、勝手にメールアドレスを使用されてしまう可能性も考えられるため、あまりよろしくないですね。
実際、Firebase Authenticationにおけるメール/パスワード認証において、デフォルトでは前述のような仕様になっています。
- ログインIDは正しい(存在する)が、パスワードが違う時のHTTPレスポンス
{
error: {
code: 400,
message: "INVALID_PASSWORD",
errors: [
{
message: "INVALID_PASSWORD",
domain: "global",
reason: "invalid"
}
]
}
}
- ログインIDが存在しない時のHTTPレスポンス
{
error: {
code: 400,
message: "EMAIL_NOT_FOUND",
errors: [
{
message: "EMAIL_NOT_FOUND",
domain: "global",
reason: "invalid"
}
]
}
}
対策方法としては、公式ドキュメントにもある方法で「メール列挙保護を有効」にすればOKです。
メール列挙保護が有効になると、レスポンスはこのように変わり、ログインIDであるメールアドレスがFirebase Authentication上にユーザ登録されているかどうかが判別できなくなります。
- メール列挙保護有効化後の認証失敗時のHTTPレスポンス
{
error: {
code: 400,
message: "INVALID_LOGIN_CREDENTIALS",
errors: [
{
message: "INVALID_LOGIN_CREDENTIALS",
domain: "global",
reason: "invalid"
}
]
}
}
実は他にもメールアドレスの列挙方法はありますが、メール列挙保護が有効であれば対策可能です。
例えば、Firebaseのクライアントで使用するJavaScript SDKでいうfetchSignInMethodsForEmail
APIや、REST APIとして公開されているメール用のフェッチプロバイダーAPIを悪用することで、ログインのレスポンスの差分を利用する方法と同様にメールアドレスの列挙ができてしまうのです。
前述のAPIは、どちらも認証されていない状態で使用することを想定されたAPIで、「ユーザのメールアドレスに紐づくすべての認証プロバイダを取得する」ために使用するものになります。
APIのパラメータとしてもidentifier
としてメールアドレスを指定してリクエストし、対象のメールアドレスに紐づく認証プロバイダの一覧を返してくれます。
これを攻撃者の視点で考えると、「認証プロバイダの一覧がレスポンスされる=そのメールアドレスでユーザ登録されている」と考えられるわけです。
メール列挙保護を有効にしていない状態でこのAPIを実行した際のHTTPレスポンスは下記のようになります。
- 対象のメールアドレスで登録されたアカウントが無い時
{
"kind": "identitytoolkit#CreateAuthUriResponse",
"registered": false,
"sessionId": "hogehoge"
}
- 対象のメールアドレスで登録されたアカウントが存在する時
{
kind: "identitytoolkit#CreateAuthUriResponse",
allProviders: [
"password"
],
registered: true,
sessionId: "hogehoge",
signinMethods: [
"password"
]
}
signinMethods
の配列にある値が、リクエストパラメータに入力したメールアドレスに紐づく認証プロバイダになります。password
はメール/パスワード認証を表す文字列です。
さて、メール列挙保護を有効にするとどうなるでしょう?
メール列挙保護を有効にすると、このAPIは認証プロバイダの一覧をレスポンスしなくなるようです。
具体的にはHTTPレスポンスは下記のようになります。
{
"kind": "identitytoolkit#CreateAuthUriResponse",
"sessionId": "hogehoge"
}
上記の通り、認証プロバイダの一覧をレスポンスしていません。
ですので、逆に言うとfetchSignInMethodsForEmail
APIを使用して、認証プロバイダをクライアント側で自動判別している場合、メール列挙保護を有効にしてしまうとクライアントアプリケーションが正常動作しなくなります。
一応、このブログを執筆している2023年9月時点では、メール列挙保護では次の機能があると説明されています。
- ログインの無効なケースでは、INVALID_LOGIN_CREDENTIALS エラー レスポンスが返されます。無効な登録のケースでは EMAIL_EXISTS が返されます。
- メール確認フローのエラー レスポンスを削除します。メールアドレスが存在する場合は、確認メールが送信されます。存在していない場合は、確認メールは送信されません。メール確認フローなしではユーザーが登録できないようにすることをおすすめします。
- ユーザーが最初に新しいアドレスを確認しなくてもメールアドレスを変更できるようにする機能を無効にします。
- createAuthUri を呼び出すときに、指定したメールアドレスのログイン方法のリストを無効にします。
ちょっとこの説明だけだと気づきにくいのですが、4点目のcreateAuthUri
の話がこれに該当します。
メール列挙保護の有効化を考える際は、この点も加味して対策実施を検討しましょう。
パスワードポリシーの問題点について
次はパスワードポリシーです。
いきなりですが、Firebase Authenticationのパスワードポリシーは「6文字以上だったらなんでもOK」です。(つまり、123456
もパスワードとして設定可能です。)
そして、このパスワードポリシーをFirebaseだけで変更することはできません。
パスワードポリシーのベストプラクティスは年々変化しているところもありますが、このブログ執筆時点(2023年9月)では、文字数だけの話で言えばPCIDSS v4.0における要件では12文字以上が推奨されています。
他にも文字種の話や最大桁数の話などもありますが、この対策はどうすべきでしょうか?
対策方法としては緩和策と根本対策の2案あります。
緩和策は、クライアント側で制御することです。
例えば、WebアプリケーションならばJavaScriptでパスワード設定ボタン押下時にバリデーションチェックをして、問題ない時に実際にパスワード変更のAPIを叩きます。
これでも、もちろんFirebase AuthenticationのAPIに直接アクセスすることで脆弱なパスワードを設定することは可能です。が、そこまでしで脆弱なパスワードを設定したいユーザって・・・?みたいな話にもなるので、(対象のアプリケーションにおけるセキュリティ要件にもよりますが)多くの場合はこの対策で十分と言っても良いでしょう。
根本対策としては、Firebase AuthenticationをGCPのIdentity Platformと連携(アップグレード)して、Identity Platform側でパスワードポリシーを強制することです。
このブログを執筆している2023年9月時点で、Identity Platformでは以下のパスワード要件がサポートされています。
- 小文字は必須です
- 大文字は必須です
- 数字は必須です
- 英数字以外の文字は必須です
- パスワードの最小文字数(6~30 文字。デフォルトは 6)
- パスワードの最大文字数(最大 4,096 文字)
これによってパスワードポリシーを変更することができますが、デメリットとしてはIdentity Platformにアップグレードすることでお金がかかることです。
セキュリティ要件や、どの程度の規模でのユーザ登録を見込むかなどを加味して、対策の導入を検討する必要がありそうです。
セルフサインアップ/アカウントデリートが想定されていないアプリケーションの問題点
最後はセルフサインアップ/アカウントデリートが想定されていない設計で起こる問題点です。
これは実際のFirebase診断でも検出があった話なのですが、WebアプリケーションやスマートフォンアプリケーションのUIからのみアカウント登録ができる前提でバックエンドアプリケーションが構成されている場合に、Firebase AuthenticationのAPIに直接アクセスすることでサインアップ/アカウントデリートできることがセキュリティ上の問題として顕在化することがあります。
厳密に言えば、この問題自体はFirebase Authenticationで起こる問題ではなく、セルフサインアップ/アカウントデリートを想定していないFirestoreやCloud Storageのセキュリティルールであったり、独自バックエンドアプリケーションにおける問題点です。しかしながら、ユーザの登録・削除に関する話題ですのでFirebase Authenticationの話題に含めてご説明します。
実際にあった話で紹介すると、Firebase Authenticationを利用しているそのアプリケーションでは、以下のようなアカウント登録フローとなっていました。
- ユーザがスマホアプリからメールアドレスを入力する
- ユーザが受信したメールにあるURLにアクセスする
- アカウント登録画面が表示され、パスワードやその他のユーザ情報を入力して登録できる
- 登録すると、スマホアプリのログイン画面からログインしてアプリ機能を利用できるようになる
意図としては、実際にメールアドレスを持っているユーザだけがアカウント登録できるようにしたい、というところでしょう。(Firebase Authentication自体に、登録されたユーザのメールアドレス所持検証をする機能もあるのですが、それを使う場合はアカウント登録⇒メール検証の順序になります。)
さて、ここには大きな問題が潜んでいることにお気づきでしょうか?
Firebase AuthenticationはIDaaSとして利用できるプロダクトで、認証に関わる様々なバックエンドがデフォルトで提供されています。つまり、クライアントアプリ側で新規登録画面やアカウント削除画面が無くとも、直接バックエンドにアクセスすればアカウント登録もアカウント削除もできるわけです。
そのことが想定されず、上記の例においては(FirestoreやCloud Storageのセキュリティルールにおいてユーザのメール検証済みかどうかの検証もしていなかった事も重なり)、Firebase Authenticationのバックエンドに直接アクセスすることで、実際には存在しないメールアドレスでアカウント登録し、サービスの機能を利用することができました。同様に、他人のメールアドレスを勝手に使用して登録することもできるわけです。
対策方法については、Firebase Authenticationではなく、Firebase Authenticationの認証情報を利用する独自バックエンドやFirebaseプロダクトにおいて、きちんとユーザ自身がセルフサインアップ/セルフアカウントデリートをできる前提でセキュリティルールや設定を考慮することです。
対策方法の一例としては、Firebase Authenticationにはカスタムクレーム(カスタム認証とは別の話です)という仕組みがあり、ユーザに属性情報を付与することができます。この属性情報の設定には通常Admin SDKを利用します。
この仕組みを利用して、独自バックエンド経由でアカウント登録した時には、ユーザの属性情報としてロール情報などをカスタムクレームに設定し、Firestoreなどの別プロダクトや独自バックエンドにおいてはこのロール情報をチェックするようにします。
そうすると、セルフサインアップしたアカウントにはカスタムクレームが設定されていないので、カスタムクレームが設定されていない場合はアクセスを拒否するといった対策が考えられます。
マルチテナンシーのケースについて
少しニッチなシチュエーションかもしれませんが、「Firebase Authentication利用しているけれど、マルチテナンシーで利用しているからREST APIは使えないはずなので大丈夫」という方もいらっしゃるかも知れません。
マルチテナンシーとは、ざっくり言うとFirebase AuthenticationをIdentity Platformにアップグレードして利用できる機能で、認証プラットフォームをマルチテナント化して、各テナントごとに認証プロバイダの設定やユーザ情報の管理ができるものです。
マルチテナンシーを利用していても、当然ですが正規のアプリからAPI KeyやプロジェクトID、それとテナントIDを取得して、攻撃者にクライアントアプリを自作された場合は各種Firebase AuthenticationのAPIにアクセスできます。
それと、実は公式のドキュメントには書かれていませんが、REST APIでもマルチテナンシーには対応可能です。※ドキュメントには書かれてない話なので、そのうち仕様が変わっている可能性もありますが、2023年9月時点では可能ですのでご紹介しておきます。
メール列挙保護の所で話を出したメール用のフェッチプロバイダーのREST APIを例に紹介しましょう。このAPIは、リクエストに設定したメールアドレスに紐づくログイン認証プロバイダの一覧をレスポンスしてくれます。
・メール用のフェッチプロバイダーのREST APIの基本情報
- Method:
POST
- Content-Type:
application/json
- Endpoint:
https://identitytoolkit.googleapis.com/
v1/accounts:createAuthUri?key=[API_KEY] - Body Payloads
- identifier: メールアドレスを設定
- continueUri: 任意のURLでよい
マルチテナンシーの場合には、このまま使用しても"registered": false
のレスポンスが返ってくるだけなのですが、実はURLのGETクエリストリングにtenantId
パラメータを追加すると動作します。ボディのJSONに含めてもどちらでも動作するようです。
マルチテナンシーに対応したREST APIアクセスをCurlコマンドにするとこのような形になります。
GETクエリストリングのケース
curl -X POST -H "Content-Type: application/json" /
-d '{"identifier":"ubsec@example.com", "continueUri": "http://example.com/"}' /
--url 'https://identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key=AIXXXXXXXXXXXXXXXXXXXXXXXXX&tenantId=mytenantid-12345'
ボディのJSONに追加するケース
curl -X POST -H "Content-Type: application/json" /
-d '{"identifier":"ubsec@example.com", "continueUri": "http://example.com/", "tenantId":"mytenantid-12345"}' /
--url 'https://identitytoolkit.googleapis.com/v1/accounts:createAuthUri?key=AIXXXXXXXXXXXXXXXXXXXXXXXXX'
個人的な感覚値かもしれませんが、このくらいは悪意を持ってハッキングする人ならとりあえず試そうとする範疇かと思いますので、マルチテナンシーだからといって攻撃されにくいと考えるのは控えるべきかと思います。
あとがき
今回はFirebaseブログ第1弾として、Firebaseサービスの紹介とFirebase Authenticationにおいて作り込まれる可能性があるセキュリティ上の問題について紹介しました。IDaaSとは言え、何も考えずに使えばセキュアになっている訳ではないという点が分かっていただけるといいなぁと思いますが、FirestoreやStorageにおけるセキュリティルールで気にすべきセキュリティ上の問題点に比べたらシンプル/軽量な方かと思います。是非、Firebaseでのアプリ開発におけるセキュリティを考えるにあたっての参考として活用いただけますと幸いです。
次回はFirebaseブログ第2弾として「Cloud Firestore」に関するブログを執筆予定ですので、そちらもぜひご覧ください。
また、以下に弊社のFirebase診断のサービス紹介ページを掲載しています。Firebaseを利用した開発におけるセキュリティ上の懸念をお持ちの方や、実際にセキュリティ診断をご検討されている方はぜひ下記ページをご参照の上、ご相談いただければと思います。