Flatt Security Blog

株式会社Flatt Securityの公式ブログです。プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

株式会社Flatt Securityの公式ブログです。
プロダクト開発やプロダクトセキュリティに関する技術的な知見・トレンドを伝える記事を発信しています。

Firebaseにおけるセキュリティの概要と実践

f:id:flattsecurity:20200409001242p:plain

こんにちは。株式会社 Flatt Security セキュリティエンジニアの梅内(@Sz4rny)です。

本記事では、主にFirebaseの概要やセキュリティルールを用いた堅牢なCloud Firestore環境の構築について説明します。本記事を読むことで、Firebaseに関する基礎知識やデータベースにおけるセキュリティ上の懸念事項について理解するとともに、セキュリティルールを用いて堅牢なCloud Firestore環境を構築するための初歩を身につけることができるでしょう。

また、Flatt Securityでは開発/運用中のプロダクトにおいて、Firebaseをセキュアに活用できているか診断することも可能です。 Firebase を用いた開発におけるセキュリティ上の懸念事項が気になる場合や、実際に診断について相談したいという場合は、ぜひ下記バナーからお問い合わせください。

Firebaseとは

Firebaseの概要

Firebaseは、Googleが提供するmBaaS(mobile Backend as a Service)です。

mBaaSとは、モバイルアプリケーションの実装に必要なバックエンド環境を提供するサービスのことを指します。1mBaaSを活用することで、バックエンド環境を独自に構築するコストを削減できるとともに、本来の目的であるアプリケーションの開発に注力することができます。

mBaaSでは、一般的に以下のような機能が提供されます。

  • 認証(ユーザ管理)
  • データベース
  • ストレージ
  • サーバレスコンピューティング

さて、本記事で説明するFirebaseはGCP(Google Cloud Platform)の一部として、主に以下に挙げるサービスを提供しています。

サービス名 概要
Authentication ユーザ認証及びユーザ管理のためのバックエンドサービスやUIライブラリ。
Cloud Firestore NoSQL型のデータベースサービス。データをドキュメントのコレクションとして格納することが特徴です。
Realtime Database NoSQL型のデータベースサービス。データをJSONツリーとして格納することが特徴です。
Cloud Storage オブジェクトストレージサービス。静的ファイルの格納や配信に利用できます。
Hosting コンテンツのホスティングサービス。デプロイされたコンテンツはグローバルCDNより配信されます。
Cloud Functions 特定のイベントをトリガーとして、バックエンドコードを自動的に実行するコンピューティングサービス。
ML Kit 機械学習のためのSDK。テキスト認識機能や翻訳機能が利用できます。

なお、以下ではCloud Firestoreを簡潔にFirestoreと表記します。

Firestoreの概要

前述の通り、FirestoreはNoSQL型のデータベースサービスです。

Firestoreの特徴は、データをドキュメントコレクションとして扱うことです。以下の画像に示す通り、コレクションは複数のドキュメントを保持します。また、各ドキュメントは一連のデータ(キーと値のペア)、もしくは、サブコレクションをそれぞれ保持します。サブコレクション内では、コレクションと同様にドキュメントやデータが保持されます。

f:id:flattsecurity:20200409192826j:plain

例えば、とあるアプリケーションのアカウントを管理するためにFirestoreを活用する場合を想定します。

この場合は「アカウントを格納するコレクションを作成し、アカウント1件をドキュメント1つと対応させる。そして、各ドキュメントはそのアカウントを示す属性情報をデータとして保持する」といった構成が考えられるでしょう。

このように、Firestoreではコレクションとドキュメントという概念を活用することで、分かりやすく拡張性の高いデータベースを実現することができます。

本章の内容を踏まえて、次章以降ではFirebaseのセキュリティについてデータベースの観点から俯瞰するとともに、セキュリティルールを用いたFirestoreの保護について説明します。

データベースセキュリティ

データベースにおけるセキュリティ上の懸念事項

Firebaseにおけるデータベースのセキュリティについて話を進める前に、まず一般論として「データベースに関するセキュリティ上の懸念事項とはどのようなものがあるのか」について検討してきましょう。

データベース・セキュリティ・コンソーシアムが提供するデータベースセキュリティガイドライン2では、データベースに対する不正アクションを以下の通り定義しています。

● 入手できる情報の悪用 (持ち出し)

● 入手できる情報の改ざん、破壊

● 業務妨害 (リソース枯渇)

● DBMS情報の不正改ざん、破壊 (更新)

● DBMS情報の不正入手 (参照)

このうち「業務妨害 (リソース枯渇)」、「DBMS情報の不正改ざん、破壊 (更新)」、「DBMS情報の不正入手 (参照)」については、Firebaseにおいて考慮する必要はありません。なぜなら、リソースやDBMS情報の管理はFirebaseの責任の範囲内だからです3

さて、考慮する必要があるのは「入手できる情報の悪用 (持ち出し)」と「入手できる情報の改ざん、破壊」です。以下で順に見ていきましょう。

入手できる情報の悪用 (持ち出し)

これは、読み込めるべきではない情報が読み込める状態を指しています。

例えば、とあるアプリケーションを利用しているユーザの個人情報(名前、年齢、性別等)をFirestoreに格納している場合を想定しましょう。この場合、もし、あるユーザが他のユーザの個人情報を取得することができてしまったら大変な問題となるでしょう。

入手できる情報の改ざん、破壊

これは、書き込めるべきではない情報が書き込める状態及び消せるべきではない情報が消せる状態を指しています。

ここでも同様に、とあるアプリケーションを利用しているユーザの個人情報をFirestoreに格納している場合を想定しましょう。この場合、もし、あるユーザが他のユーザの個人情報を作成したり、すでに存在する他のユーザの個人情報を更新したり、それを削除したりすることができてしまったら大変な問題となるでしょう。

まとめ

上記をまとめると、データベースにおけるセキュリティ上の懸念事項は以下の3点であると言えるでしょう。

  • 読み込めるべきではない情報が読み込める。つまり、読み込み権限の設定不備。
  • 書き込めるべきではない情報が書き込める。つまり、作成権限及び更新権限の設定不備。
  • 削除できるべきではない情報が削除できる。つまり、削除権限の設定不備。

Firebaseでは、セキュリティルールを設定することで、これらの懸念事項に対処することができます。次章では、Firestoreのセキュリティルールについて詳しく説明します。

Firestoreのセキュリティルール

はじめに

Firebaseでは、データベースサービスやストレージサービスにおける権限管理を実現するために、セキュリティルールを用いた権限管理の仕組みが存在します。

セキュリティルールを設定できるサービスは以下の通りです。4

  • Firestore
  • Realtime Database
  • Cloud Storage

これらのうち、本章ではFirestoreにおけるセキュリティルールについて説明します。

基礎文法

Firestoreのセキュリティルールでは、特定のパスへの特定のリクエストに対する権限を定義します。具体的には、matchブロックに指定されたパスに対応する箇所においてallowブロックに記述されている操作が許可されます。

以下に、myCollectionというコレクションに含まれる任意のドキュメントに対する読み込み操作を許可するセキュリティルールの例を示します。5

service cloud.firestore { // サービスの指定
  match /databases/{database}/documents {
    match /myCollection/{document} { // パスの指定
      allow read; // 許可する操作の指定
    }
  }
}

なお、前述したリクエストとは、allowステートメント内で呼び出される1つ以上のメソッドのことです。サポートされているメソッドを以下に示します。

  • get : 単一のドキュメントに対する読み込み操作。
  • list : 複数のドキュメントを対象とするクエリやコレクションに対する読み込み操作。
  • create : ドキュメントの作成操作。
  • update : ドキュメントの更新操作。
  • delete : ドキュメントの削除操作。

なお、これほどきめ細かく制御する必要がない場合は、readwriteを用います。readget及びlistを、writecreateupdate及びdeleteをそれぞれ包含します。

さて、その他の重要な点としてallowブロックに条件が設定できることが挙げられます。先述のセキュリティルールの例において、Authenticationによる認証が完了しているユーザにのみ読み込み操作を許可したい場合には、以下のようにifブロックを追加することで条件を記述します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /myCollection/{document} {
      allow read: if request.auth != null;
    }
  }
}

カスタム関数

プログラムの実装において、関連する複数の処理を1つの関数(カスタム関数)としてまとめることで、プログラムの見通しが良くなる場合があります。これと同じように、セキュリティルールにおいても関連する条件群を単一の関数としてまとめることで、ルールを簡潔に記述できるようになります。

以下の例を見てみましょう。

service cloud.firestore {
  match /databases/{database}/documents {
    match /fooCollection/{uid} {
      allow read, write: if request.auth != null && request.auth.uid == uid;
    }
        
    match /barCollection/{uid} {
      allow read, write: if request.auth != null && request.auth.uid == uid;
    }
  }
}

/fooCollection/{uid}及び/barCollection/{uid}の双方において、「認証されており、かつ、パス中のuidの値(ドキュメント)がAuthenticationにより付与されたuidの値と一致するか」という条件が記述されています。もちろんこのままでも正しく動作しますが、これらを関数として記述することでセキュリティルールの見通しが良くなります。なお、関数内では一連の条件式を含んだ単一のreturnステートメントのみを記述します。

以下に、重複した条件群をsignedInAndCorrectDocument関数としてまとめた際のセキュリティルールの例を示します。

service cloud.firestore {
  match /databases/{database}/documents {
    function signedInAndCorrectDocument(uid) {
      return request.auth != null && uid == request.auth.uid;
    }
    match /fooCollection/{uid} {
      allow read, write: if signedInAndCorrectDocument(uid);
    }
    match /barCollection/{uid} {
      allow read, write: if signedInAndCorrectDocument(uid);
    }
  }
}

変数と関数

認証やリクエスト、書き込まれるデータの内容等に基づいてセキュリティルールを柔軟に構成できるようにするために、セキュリティルールにおいてはいくつかの組み込み変数や組み込み関数が用意されています。

以下に、その例を抜粋して示します。

  • 組み込み変数
    • request : 認証情報やリクエストパス、クエリパラメータといった値を含んだオブジェクト。
      • request.auth : Authenticationによって認証済みの場合はuid及びtokenが設定されます。それ以外の場合はnullが設定されます。
      • request.resource.data : データ書き込み系のリクエスト時に付与される値で、書き込もうとしているデータの情報が含まれます。
      • request.time : リクエストの際の時刻を示すタイムスタンプが設定されます。
    • resource.data : リクエストが対象とするパスに存在するデータの情報が含まれます。
  • 組み込み関数
    • get(path) : pathに指定されたパスに存在するデータを返します。
    • exists(path) : pathに指定されたパスにデータが存在するかどうかを判定します。

上記に加えて、特定の型の変数から呼び出し可能ないくつかのメソッドも用意されています。以下にその例を抜粋して示します。

  • String.matches(regexp) : Stringが正規表現regexpにマッチするかどうかを判定します。
  • String.size() : Stringの文字列長を返します。
  • Map.keys() : Mapに含まれるキーをListとして返します。
  • List.size() : Listに含まれる要素数を返します。
  • List.hasAll(list) : 引数listの全ての要素がListに含まれているかどうかを判定します。つまり、A.hasAll(B)A ⊇ Bを満たすかどうかを判定します。
  • List.hasAny(list) : 引数listの要素のうち少なくとも1つ以上がListに含まれているかどうかを判定します。つまり、A.hasAny(B)∃b∈B, b∈Aを満たすかどうかを判定します。
  • List.hasOnly(list) : 引数listに含まれる要素のみでListが構成されているかどうかを判定します。つまり、A.hasOnly(B)A ⊆ Bを満たすかどうかを判定します。

これらの組み込み関数や組み込み変数、メソッドをうまく利用することで、柔軟にセキュリティルールを構成することが可能になります。

セキュリティルールの例

簡素なSNSアプリケーションを題材に、以下の要件を満たすセキュリティルールを構成してみましょう。

  • Firestoreでユーザ情報と投稿情報を格納する。
  • ユーザ情報に関する要件
    • /users/{uid}に格納する。なお、uidはAuthenticationにより付与されたuidの値である。
    • データに関する要件
      • 格納するデータはユーザ名(name)、プロフィール(profile)である。
      • ユーザ名は20文字以内の文字列である。
      • プロフィールは100文字以内の文字列である。
      • これら以外のデータを格納することは許可しない。
    • 権限に関する要件
      • 任意のユーザが認証無しでユーザ情報を閲覧できる。
      • 認証を受けたユーザは自身のユーザ情報を作成及び更新できる。
      • ユーザ種別がadminのユーザのみユーザ情報を削除できる。
  • 投稿情報に関する要件
    • /posts/{uid}に格納する。なお、uidはAuthenticationにより付与されたuidの値である。
    • データに関する要件
      • 格納するデータはタイトル(title)、本文(body)、カテゴリ(category)、投稿日時(timestamp)である。
      • タイトルは50文字以内の文字列である。
      • 本文は1000文字以内の文字列である。
      • カテゴリは20文字以内の文字列である。なお、存在しなくてもよい。
      • 投稿日時はリクエストの日時と一致するタイムスタンプである。
      • これら以外のデータを格納することは許可しない。
    • 権限に関する要件
      • 任意のユーザが認証無しで投稿情報を閲覧できる。
      • 認証を受けたユーザは自身の投稿情報を作成及び削除できる。
      • 投稿情報の更新は許可しない。

上記の要件を満たすセキュリティルールの例を以下に示します。ぜひ自身でセキュリティルールを記述して、以下のセキュリティルールと見比べてみてください。

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read;
      allow create, update: if signedIn() &&
                               isOwnPath(uid) &&
                               validateUserData(request.resource.data);
      allow delete: if signedIn() && 
                       isAdmin();
    }  

    match /posts/{uid} {
      allow read;
      allow create, delete: if signedIn() &&
                               isOwnPath(uid) &&
                               validatePostData(request.resource.data);
      allow update: if false;
    }

    function signedIn() {
      return request.auth.uid != null;
    }

    function isOwnPath(uid) {
      return uid == request.auth.uid;
    }
    
    function isAdmin() {
      return request.auth.token.admin == true;
    }

    function validateUserData(data) {
      return data.keys().hasAll(["name", "profile"]) &&
             data.keys().hasOnly(["name", "profile"]) &&
             data.name is string && data.name.size() <= 20 &&
             data.profile is string && data.profile.size() <= 100 &&
             data.role is string && data.role in ["user", "admin"]
    }

    function validatePostData(data) {
      return data.keys().hasAll(["title", "body", "timestamp"]) &&
             data.keys().hasOnly(["title", "body", "category", "timestamp"]) &&
             data.title is string && data.title.size() <= 50 &&
             data.body is string && data.body.size() <= 1000 &&
             (data.category == null || (data.category is string && data.category.size() <= 20)) &&
             data.timestamp is timestamp && data.timestamp == request.time;
    }
  }
}

脆弱なセキュリティルール

ここからは脆弱なセキュリティルールについて、事例を踏まえながら説明します。

オープンアクセス

オープンアクセス、つまり誰でも任意のパスに対して任意の操作が可能である場合、悪意のある第三者によってデータを不正に窃取、改ざんされる恐れがあります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

認証と認可の同一視

認証と認可を分けて考えていない場合、適切なアクセスコントロールが行われない恐れがあります。

例えば、以下のセキュリティルールは認証を行なったユーザに対しあらゆる権限を付与してしまっているため、オープンアクセスの場合と同様にデータを不正に窃取、改ざんされる恐れがあります。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid != null;
    }
  }
}

パスの重複

特定のパスに対するセキュリティルールが重複して定義されている場合、想定外の権限が付与される恐れがあります。

例えば、以下のような要件を想定します。

  • 誰でも任意のコレクション及びドキュメントに格納されたデータを読み込める。
  • publicコレクションに含まれるドキュメント及びデータはadminのみが書き込める。

これらの要件に基づいたセキュリティルールとして、以下のような例が考えられます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
    }

    match /public/{document=**} {
      allow write: if request.auth.token.admin == true;
    }
  }
}

さて、その後に、以下のような要件が追加されることになりました。

  • privateコレクションに含まれるドキュメント及びデータはadminのみが読み込み及び書き込める(例外的にadmin以外は読み込めないようにする)。

上記の要件を満たすために、先ほどのセキュリティルールに以下の通り追記を行いました。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read;
    }

    match /public/{document=**} {
      allow write: if request.auth.token.admin == true;
    }
    
    match /private/{document=**} {
      allow read, write: if request.auth.token.admin == true;
    }
  }
}

一見正しいようですが、パスが重複している箇所に脆弱性が存在します。

上記のセキュリティルールにおいて、privateコレクションはadminのみが閲覧できるように見えます。しかし、その上の/{document=**}の箇所おいて任意のユーザに対してread権限を与えているため、/private/{document=**}は誰でも閲覧することができてしまいます。

このような問題は、セキュリティルールの評価において、該当するルールが全て同等に評価されることに起因します。つまり、/private/{document=**}/{document=**}よりも厳密にリクエストのパスにマッチするから、といった理由で特定のルールが優先されることはありません。

そのため、可能な限り影響する範囲が広い複数のセグメントをワイルドカードとして宣言することは避け、よりきめ細やかな制御を行うことが好ましいと言えるでしょう。

不必要なフィールドの許可

不必要なフィールドを持つデータの作成や更新が許可されている場合、想定外の動作を引き起こす恐れがあります。

例えば、以下のような要件を想定します。

  • Firestoreでユーザ情報を格納する。
  • ユーザ情報に関する要件
    • /users/{uid}に格納する。なお、uidはAuthenticationにより付与されたuidの値である。
    • データに関する要件
      • 格納するデータはユーザ名(name)、プロフィール(profile)、ユーザ種別(role)である。
      • ユーザ名は20文字以内の文字列である。
      • プロフィールは100文字以内の文字列である。
      • ユーザ種別はguestadminのどちらかである。なお、ユーザ種別はユーザ情報の作成をトリガとしてFunctionsで定義された関数によって自動的に付与されるため、セキュリティルール上で���記する必要はない。

これらの要件に基づいたセキュリティルールとして、以下のようなものが考えられます。

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow create: if signedIn() &&
                       isOwnPath(uid) &&
                       validateUserData(request.resource.data);
    }

    function signedIn() {
      return request.auth.uid != null;
    }

    function isOwnPath(uid) {
      return uid == request.auth.uid;
    }

    function validateUserData(data) {
      return data.name is string && data.name.size() <= 20 &&
             data.profile is string && data.profile.size() <= 100;
    }
  }
}

一見正しいようですが、validateUserData関数にチェック漏れが存在します。

当該関数では、確かにリクエストされたデータに含まれるフィールドの型やそのコンテンツの内容が要件に適合するかどうかをチェックしています。しかしながら、当該データにどのようなフィールドが含まれていてもよいのかついてはチェックされていません。そのため、もし以下のようなデータがリクエストに含まれていた場合、不正にユーザ種別を設定されてしまう恐れがあります。

{
  "name": "Flatt Taro",
  "profile": "Hello, Firebase!",
  "role": "admin"
}

このような問題を防ぐためには、データに含まれるフィールドをList.hasAll(list)メソッドやList.hasOnly(list)メソッドを用いて制限することが有効です。

今回の場合は、validateUserData関数を以下のように修正し、データに含まれるフィールドをチェックするとよいでしょう。

function validateUserData(data) {
  return data.keys().hasAll(["name", "profile"]) && 
         data.keys().hasOnly(["name", "profile"]) &&
         data.name is string && data.name.size() <= 20 &&
         data.profile is string && data.profile.size() <= 100;
}

なお、データに含まれるフィールドが特定のスキーマに一致するように厳密に制限したい場合、上記のようにList.hasAll(list)List.hasOnly(list)を同時に用いることが有効です。なぜなら、A.hasAll(B) && A.hasOnly(B)trueである時A ⊇ B ∧ A ⊆ Btrueである、つまり、A = Bであると言えるからです。

堅牢なセキュリティルールを構成するために

ここまでを通して、セキュリティルールの基礎や脆弱なセキュリティルールの例について理解できたかと思います。では、実際にセキュリティルールを構成する際にはどのような点に気をつけなければならないのでしょうか。

まず、第一に考えなければならないことはデータベースそのものの設計についてです。

例えば、機密情報を含むデータと外部に公開してもよいデータが同一のコレクションに含まれている場合、適切なセキュリティルールを構成することが難しくなります。たとえ構成できたとしても、特定のパス、操作に対する条件式が非常に複雑になってしまい、ひいてはそれがセキュリティルールの不備の原因になったり、仕様変更に伴うセキュリティルールの更新が困難になったりすることに繋がりかねません。Firestoreを活用する際には、まずFirestore自身の特徴を理解し、コレクション、ドキュメント、そしてサブコレクションという単位をうまく扱うことが求められると言えるでしょう。

次に考えなければならないことは、想定される全てのパスに対する各メソッド6の権限についてです。

つまり、全てのパスの各メソッドについて、誰が、どのような場合にそのメソッドを実行してよいのかを洗い出す必要があります。このうち、「誰が」については、ユーザの認証状態や認証済みユーザの種別(role)をもとに判断することで、「どのような場合に」については、主に作成及び更新時のリクエストに付与されたデータをチェックすることを指しています。そして、洗い出された要件を基に、実際にセキュリティルールとして記述していくとよいでしょう。この時、条件式が複雑にならないように適宜関数として括り出すのもよい方法です。

以下に、セキュリティルールの構成に必要な要件を洗い出す際の例を示します

パス名 /databases/{database}/documents/users/{uid}
read 誰でも参照可能にする。
create Authenticationにより付与された自身のuidの値と一致するドキュメントのみ許可する。データが有するフィールドは名前及びプロフィールとし、これら以外は許可しない。
update Authenticationにより付与された自身のuidの値と一致するドキュメントのみ許可する。データが有するフィールドはプロフィールのみとし、これら以外は許可しない(名前の更新は許可しない)。
delete 削除は許可しない。

なお、セキュリティルールに記述されていないパスに対する操作やメソッドの利用はデフォルトで拒否されますが、可読性のために明示的に記述しておいたほうが良いでしょう。

以下は、上記の要件例のうち、deleteを拒否することを明示的に記述した例です。

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{uid} {
      allow read: if ...;
      allow create: if ...;
      allow update: if ...;
      allow delete: if false; // 明示的に拒否
    }
  }
}

最後は、セキュリティルールのテストについてです。

上記の手順のように綿密に要件を洗い出し、それに従ってセキュリティルールを記述したとしても、やはりどこかに脆弱性が潜んでいるリスクは拭いきれません。そこで便利なのが、セキュリティルールをテストするためのシミュレータエミュレータです。

このうち、シミュレータはFirestoreのコンソールから利用することができます。Firestoreのコンソールからルールタブを開くと表示されるルールプレイグラウンドから、特定の条件を持つリクエストがセキュリティルールによって許可されるか(もしくは、拒否されるか)をテストすることができます。Firestoreにおけるデータベースの規模が小さかったり、簡易的なテストをするだけなのであればシミュレータで十分でしょう。

一方のエミュレータは、セキュリティルールをローカルで検証するためのものです。シミュレータによるテストの場合、毎回Firestoreのコンソールにアクセスした上でテストする条件を手動で記述する必要があるため、Firestoreにおけるデータベースの規模が大きかったり、多数の条件をテストしたりする場合は非常に煩雑になります。エミュレータはこの問題を解決するものです。具体的には、FirestoreのエミュレータとなるAPIエンドポイントを立ち上げ、テスト用スクリプトをそこで実行することで検証を行います。firebase / quickstart-nodejs/firestore-emulator/javascript-quickstart では、エミュレータを立ち上げるための方法やテスト用スクリプトのサンプルが公開されており、ダウンロードすることですぐにローカルで試すことができます。エミュレータを用いてセキュリティルールの検証を行う場合は、こちらを参照してみてください。

おわりに

本記事ではFirebaseの概要やセキュリティルールを用いた堅牢なFirestore環境の構築について説明しました。

本記事を通して、Firebaseに関する基礎知識やデータベースにおけるセキュリティ上の懸念事項について理解するとともに、セキュリティルールを用いて堅牢なFirestore環境を構築するための初歩を身につけることができたでしょうか。もし、そうであれば筆者として幸いです。

Firebaseを活用することでバックエンド環境を独自に構築するコストを削減できるとともに、本来の目的であるアプリケーションの開発に注力することができます。Firebaseはとても便利なものである一方で、セキュリティについて懸念しなければならない事項が多く存在するのもまた確かです。7

本記事が、皆様のFirebaseを用いたアプリケーション開発の一助となれば幸いです。

なお、株式会社 Flatt Security (https://flatt.tech/) では、専門のセキュリティエンジニアによるFirebaseに対する脆弱性診断サービスを提供しています(もちろん、Firebaseを活用しているアプリケーション全体の診断も可能です)。もし、Firebaseを用いた開発におけるセキュリティ上の懸念事項が気になる場合や、実際に診断について相談したいという場合は、ぜひ下記バナーからお問い合わせください。

20220124130611

では、ここまでお読み頂きありがとうございました。


  1. あくまでモバイルアプリケーションの実装に適しているバックエンド環境が提供されるという意味であり、他のプラットフォームで利用できないということはありません。実際に、FirebaseはiOSやAndroidに加えてUnityやWebといった幅広いプラットフォームをサポートしています。

  2. データベース・セキュリティ・コンソーシアム (DBSC): データベースセキュリティガイドライン 第2.0版 p.10, 2009.

  3. リソース枯渇によるサービスの停止(いわゆるDenial of Service)に関して考慮する必要はありませんが、Firebaseのサービスに対して大量のリクエストを送信することで管理者に対する課金請求額を上昇させるような攻撃については考慮する必要があります。保険的な対策として、予算アラートしきい値ルールを設定し監視することが挙げられます。

  4. Realtime Databaseではセキュリティルールの記述にJSONを用いる一方で、Firestore及びCloud StorageではCEL(Common Expression Language)をベースにした独自のルール記述言語を利用します。

  5. ここで、matchブロックに指定されているパスのうち{変数名} と記述されている箇所はワイルドカードです。なお、{変数名=**}と記述することで下位にある任意のドキュメントやサブコレクションに対応させることができます。

  6. 読み込み(read またはget/list)、作成(create)、更新(update)、削除(delete)。なお、作成、更新、削除をまとめて扱うのであればwrite。 ↩

  7. 今回紹介したFirestoreのセキュリティ以外にも考慮しなければならないことは多々あります。例えば、Hostingを利用するのであればそのデプロイ構成には細心の注意を払う必要がありますし、Realtime Databaseを利用するのであれば、Firebaseのセキュリティルールとは勝手が違う、JSONを用いたセキュリティルールを記述しなければなりません。