הימנעות מכללים לא מאובטחים

במדריך הזה מוסבר איך לזהות נקודות חולשה נפוצות בהגדרות של Firebase Security Rules, איך לבדוק את הכללים שלכם ולשפר את האבטחה שלהם, ואיך לבדוק את השינויים לפני הפריסה שלהם.

אם קיבלתם התראה על כך שהנתונים שלכם לא מאובטחים כראוי, כדאי לבדוק את השגיאות הנפוצות האלה ולעדכן כללים שנמצאים בסיכון.

גישה ל-Firebase Security Rules

כדי להציג את Rules הקיים, משתמשים ב-CLI של Firebase או במסוף Firebase. חשוב לערוך את הכללים באותה שיטה, באופן עקבי, כדי להימנע מחתימה בטעות על העדכונים. אם אתם לא בטוחים שהכללים שהוגדרו באופן מקומי משקפים את העדכונים האחרונים, במסוף Firebase תמיד מוצגת הגרסה האחרונה של Firebase Security Rules שפרסמתם.

כדי לגשת לכללים ממסוף Firebase, בוחרים את הפרויקט ואז עוברים אל Realtime Database, Cloud Firestore או Storage. לוחצים על Rules אחרי שמגיעים למסד הנתונים או לקטגוריית האחסון הנכונים.

כדי לגשת לכללים מ-CLI של Firebase, עוברים לקובץ הכללים שמופיע בקובץ firebase.json.

הסבר על Firebase Security Rules

Firebase Security Rules להגן על הנתונים מפני משתמשים זדוניים. כשיוצרים מכונה של מסד נתונים או קטגוריה של Cloud Storage במסוף Firebase, אפשר לבחור אם לדחות את הגישה לכל המשתמשים (מצב נעול) או להעניק גישה לכל המשתמשים (מצב בדיקה). יכול להיות שתרצו להגדיר הגדרות פתוחות יותר במהלך הפיתוח, אבל חשוב להקדיש זמן להגדרה נכונה של הכללים ולהגנה על הנתונים לפני פריסת האפליקציה.

כשאתם מפתחים את האפליקציה ובודקים הגדרות שונות של הכללים, אתם יכולים להשתמש באחד מהמעבדים המקומיים של Firebase כדי להריץ את האפליקציה בסביבת פיתוח מקומית.

תרחישים נפוצים עם כללים לא מאובטחים

לפני פריסת האפליקציה, צריך לבדוק ולעדכן את Rules שהגדרתם כברירת מחדל או במהלך הפיתוח הראשוני של האפליקציה. כדי לוודא שאתם מאבטחים כראוי את נתוני המשתמשים, חשוב להימנע מהמלכודות הנפוצות הבאות.

גישה פתוחה

כשמגדירים את הפרויקט ב-Firebase, יכול להיות שהגדרתם את הכללים כך שיאפשרו גישה פתוחה במהלך הפיתוח. יכול להיות שאתם חושבים שאתם היחידים שמשתמשים באפליקציה שלכם, אבל אם פרסתם אותה, היא זמינה באינטרנט. אם לא מבצעים אימות משתמשים והגדרת כללי אבטחה, כל מי שיחליט לנחש את מזהה הפרויקט יוכל לגנוב, לשנות או למחוק את הנתונים.

לא מומלץ: גישת קריאה וכתיבה לכל המשתמשים.
// Allow read/write access to all users under any conditions
// Warning: **NEVER** use this ruleset in production; it allows
// anyone to overwrite your entire database.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}
{
  // Allow read/write access to all users under any conditions
  // Warning: **NEVER** use this ruleset in production; it allows
  // anyone to overwrite your entire database.

  "rules": {
    ".read": true,
    ".write": true
  }
}
    
// Anyone can read or write to the bucket, even non-users of your app.
// Because it is shared with App Engine, this will also make
// files uploaded using App Engine public.
// Warning: This rule makes every file in your Cloud Storage bucket accessible to any user.
// Apply caution before using it in production, since it means anyone
// can overwrite all your files.

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write;
    }
  }
}
    
פתרון: כללים שמגבילים את הגישה לקריאה ולכתיבה.

יוצרים כללים שתואמים להיררכיית הנתונים. אחד מהפתרונות הנפוצים לבעיה הזו הוא אבטחה מבוססת-משתמשים באמצעות Firebase Authentication. מידע נוסף על אימות משתמשים באמצעות כללים

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow only authenticated content owners access
    match /some_collection/{document} {
      // Allow reads and deletion if the current user owns the existing document
      allow read, delete: if request.auth.uid == resource.data.author_uid;
      // Allow creation if the current user owns the new document
      allow create: if request.auth.uid == request.resource.data.author_uid;
      // Allow updates by the owner, and prevent change of ownership
      allow update: if request.auth.uid == request.resource.data.author_uid
                    && request.auth.uid == resource.data.author_uid;

    }
  }
}
  
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow public read access, but only content owners can write
    match /some_collection/{document} {
      // Allow public reads
      allow read: if true
      // Allow creation if the current user owns the new document
      allow create: if request.auth.uid == request.resource.data.author_uid;
      // Allow updates by the owner, and prevent change of ownership
      allow update: if request.auth.uid == request.resource.data.author_uid
                    && request.auth.uid == resource.data.author_uid;
      // Allow deletion if the current user owns the existing document
      allow delete: if request.auth.uid == resource.data.author_uid;
    }
  }
}
  
{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "auth !== null && auth.uid === $uid",
        ".write": "auth !== null && auth.uid === $uid"
      }
    }
  }
}
    
{
  // Allow anyone to read data, but only authenticated content owners can
  // make changes to their data

  "rules": {
    "some_path/$uid": {
      ".read": true,
      // or ".read": "auth.uid !== null" for only authenticated users
      ".write": "auth.uid === $uid"
    }
  }
}
    
// Grants a user access to a node matching their user ID
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read;
      allow write: if request.auth.uid == userId;
    }
  }
}
  

גישה לכל משתמש מאומת

לפעמים, Rules בודק אם משתמש מחובר, אבל לא מגביל את הגישה על סמך האימות הזה. אם אחד מהכללים כולל את הערך auth != null, צריך לאשר שכל משתמש שמחובר לחשבון יקבל גישה לנתונים.

לא מומלץ: לכל משתמש שמחובר יש הרשאת קריאה וכתיבה לכל מסד הנתונים.
service cloud.firestore {
  match /databases/{database}/documents {
    match /some_collection/{document} {
      allow read, write: if request.auth.uid != null;
    }
  }
}
{
  "rules": {
    ".read": "auth.uid !== null",
    ".write": "auth.uid !== null"
  }
}
// Only authenticated users can read or write to the bucket
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}
הפתרון: צמצום הגישה באמצעות תנאי אבטחה.

כשבודקים אימות, כדאי גם להשתמש באחד ממאפייני האימות כדי להגביל עוד יותר את הגישה של משתמשים ספציפיים לקבוצות נתונים ספציפיות. מידע נוסף על מאפייני האימות השונים

service cloud.firestore {
  match /databases/{database}/documents {
    // Assign roles to all users and refine access based on user roles
    match /some_collection/{document} {
     allow read: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Reader"
     allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == "Writer"

     // Note: Checking for roles in your database using `get` (as in the code
     // above) or `exists` carry standard charges for read operations.
    }
  }
}
// Give each user in your database a particular attribute
// and set it to true/false
// Then, use that attribute to grant access to subsets of data
// For example, an "administrator" attribute set
// to "true" grants write access to data

service cloud.firestore {
  match /databases/{database}/documents {
    match /some_collection/{document} {
      allow write: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
      allow read: true;
    }
  }
}
  
service cloud.firestore {
  match /databases/{database}/documents {
    // Allow public read access, but only content owners can write
    match /some_collection/{document} {
      allow read: if true
      allow write: if request.auth.uid == request.resource.data.author_uid
    }
  }
}
  
{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "auth.uid === $uid",
        ".write": "auth.uid === $uid"
      }
    }
  }
}
    
{
  "rules": {
    "some_path/$uid": {
      ".write": "auth.uid === $uid",
      // Create a "public" subpath in your dataset
      "public": {
        ".read": true
        // or ".read": "auth.uid !== null"
      },
      // Create a "private" subpath in your dataset
      "private": {
        ".read": "auth.uid === $uid"
      }
    }
  }
}
    
{
  // Allow anyone to read data, but only authenticated content owners can
  // make changes to their data

  "rules": {
    "some_path/$uid": {
      ".read": true,
      // or ".read": "auth.uid !== null" for only authenticated users
      ".write": "auth.uid === $uid"
    }
  }
}
    
// Allow reads if the group ID in your token matches the file metadata `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}
// Grants a user access to a node matching their user ID
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}
service firebase.storage {
  match /b/{bucket}/o {
    // Files look like: "user/<UID>/file.txt"
    match /user/{userId}/{fileName} {
      allow read;
      allow write: if request.auth.uid == userId;
    }
  }
}
  

(Realtime Database) כללים שהועברו בירושה בצורה שגויה

Realtime Database Security Rules, כאשר כללים בנתיבים שטחיים יותר של הורה מבטלים כללים בצמתים עמוקים יותר של צאצאים. כשכותבים כלל בצומת צאצא, חשוב לזכור שהוא יכול להעניק רק הרשאות נוספות. אי אפשר לשפר או לבטל את הגישה לנתונים בנתיב עמוק יותר במסד הנתונים.

לא מומלץ: שינוי של כללים בנתיבים צאצאים
{
  "rules": {
     "foo": {
        // allows read to /foo/*
        ".read": "data.child('baz').val() === true",
        "bar": {
          /* ignored, since read was allowed already */
          ".read": false
        }
     }
  }
}
פתרון: כותבים כללים רחבים בנתיבים של ההורים ומעניקים הרשאות ספציפיות יותר בנתיבים של הצאצאים. אם נדרש רמת פירוט גבוהה יותר לגישה לנתונים, צריך לשמור על רמת פירוט גבוהה של הכללים. מידע נוסף על Realtime Database Security Rules מדורג מופיע במאמר תחביר הליבה של Realtime Database Security Rules.

גישה סגורה

בזמן הפיתוח של האפליקציה, גישה נפוצה נוספת היא לא לאפשר גישה לנתונים. בדרך כלל, המשמעות היא שחסרתם את הגישה לקריאה ולכתיבה לכל המשתמשים, באופן הבא:

// Deny read/write access to all users under any conditions
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}
{
  "rules": {
    ".read": false,
    ".write": false
  }
}
    
// Access to files through Cloud Storage is completely disallowed.
// Files may still be accessible through App Engine or Google Cloud Storage APIs.

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}

עדיין תהיה גישה למסד הנתונים שלכם דרך Firebase Admin SDK ו-Cloud Functions. יש להשתמש בכללים האלה אם אתם מתכוונים להשתמש ב-Cloud Firestore או ב-Realtime Database כקצה עורפי לשרת בלבד בשילוב עם ה-SDK של Firebase Admin. השירות מאובטח, אבל כדאי לבדוק אם לקוחות האפליקציה יכולים לאחזר נתונים בצורה תקינה.

מידע נוסף על Cloud Firestore Security Rules ועל אופן הפעולה שלו זמין במאמר תחילת העבודה עם Cloud Firestore Security Rules.

בדיקת Cloud Firestore Security Rules

כדי לבדוק את התנהגות האפליקציה ולאמת את ההגדרות של Cloud Firestore Security Rules, תוכלו להשתמש במעבד Firebase. אפשר להשתמש במהדמטור Cloud Firestore כדי להריץ בדיקות יחידה בסביבה מקומית ולהפוך אותן לאוטומטיות, לפני שפורסים שינויים.

כדי לאמת במהירות את Firebase Security Rules במסוף Firebase, אפשר להשתמש בסימולטור הכללים של Firebase.