---
title: "Firebasen tietoturvasääntöjen esimerkit: 11 hyökkäystä"
description: "Firebasen tietoturvasääntöjen esimerkit emulaattorissa: mitä kukin sääntö salli vieraalle tai toiselle käyttäjälle, mikä vuoto jäi auki ja mikä sulki sen."
url: "https://madsalt.tech/fi/blog/firebase-security-rules-examples/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/fi/about/"
language: "fi"
published: 2026-11-03
---

# Firebasen tietoturvasääntöjen esimerkit: 11 hyökkäystä

TL;DR

Lähetin samat pyynnöt 11 Firestore-sääntöjoukkoon vieraana, anonyymina käyttäjänä ja kolmena kirjautuneena käyttäjänä. `request.auth != null` salli ulkopuolisen lukea, muuttaa ja poistaa kaikkien tilaukset. Omistajatarkistus pelkän `resource`-kentän varassa antoi käyttäjän luovuttaa tilauksen jollekulle muulle. Yksi laaja `{document=**}` avasi kokoelman, jolla oli oikea omistajasääntö. Ja dokumenttiin tallennettu rooli, jota käyttäjä saa kirjoittaa, teki hänestä adminin yhdellä pyynnöllä. Jokaiseen on alla korjaus, ja korjattu sääntö kesti samat pyynnöt.

Firebase-sovelluksen selaimen ja tietokannan välissä ei ole sinun palvelintasi. Projektin konfiguraatio kulkee sivun mukana, ja [tietoturvasäännöt](https://firebase.google.com/docs/rules/insecure-rules) ovat se, mikä seisoo datasi ja jokaisen sen konfiguraation haltijan välissä. Siksi halusin esimerkkejä säännöistä hyökkäys vierellään, en listaa sääntöjä, jotka vain näyttävät oikeilta.

**Näin testasin.** Firebase Local Emulator Suite Firebase CLI 15.34.2:sta, 3. marraskuuta 2026. Latasin jokaisen sääntöjoukon, nollasin datan ja kutsuin Firestoren REST API:a oikeilla tokeneilla Auth-emulaattorista: ei tokenia, anonyymi käyttäjä, käyttäjät A ja B sekä ulkopuolinen, joka oli vain rekisteröitynyt. Data oli keksittyä: A:n tilaukset `a1` ja `a2`, B:n tilaus `b1`.

## Mitkä Firebase-säännöt vuotavat?

| Sääntö                                             | Pyyntö                                         | Tulos           |
| -------------------------------------------------- | ---------------------------------------------- | --------------- |
| `if true` (testitila)                              | Vieras listaa tilaukset                        | `200`, kaikki 3 |
| `if true` (testitila)                              | Vieras poistaa B:n tilauksen                   | `200`           |
| `request.auth != null`                             | Anonyymi käyttäjä listaa tilaukset             | `200`, kaikki 3 |
| `request.auth != null`                             | Ulkopuolinen poistaa A:n tilauksen             | `200`           |
| Omistajatarkistus vain `resource`                  | A luo tilauksen, jonka omistaja on B           | `200`           |
| Omistajatarkistus vain `resource`                  | A muuttaa oman tilauksensa `ownerId`:n         | `200`           |
| Omistajasääntö ja `{document=**}`-luku             | A lukee B:n tilauksen                          | `200`           |
| Käyttäjä saa kirjoittaa oman `users`-dokumenttinsa | A asettaa `role: admin` ja lukee B:n tilauksen | `200`           |

## Mitä testitila oikeasti avaa?

```
match /{document=**} {
  allow read, write: if true;
}
```

Kaiken, kaikille. Vieras ilman tokenia listasi kaikki 3 tilausta, poisti yhden ja loi uuden. Mikään tästä ei vaadi tiliä tai vuotanutta salaisuutta, vain projektin id:n.

Päivämäärällinen versio, jota testitila usein käyttää, epäonnistuu eri tavalla. Kun säännössä on `if request.time < timestamp.date(2026, 1, 1)` ja päivämäärä on mennyt, vieras sai `403` ja niin sai käyttäjä A lukiessaan omaa tilaustaan. Vuoto on ohi, ja sovellus myös.

## Riittääkö `request.auth != null`?

Ei. Se kertoo vain, että pyynnön takana on jokin tili.

```
match /orders/{id} {
  allow read, write: if request.auth != null;
}
```

Vieras sai `403`, minkä vuoksi tämä sääntö tuntuu turvalliselta nopeassa testissä. Sitten ulkopuolinen, joka oli vain rekisteröitynyt, listasi kaikki 3 tilausta, muutti B:n tilauksen summaa ja poisti A:n tilauksen, kaikki `200`:lla. Myös anonyymi käyttäjä listasi kaikki 3. Jos rekisteröityminen on auki, tämä sääntö tarkoittaa "kaikki, jotka viitsivät rekisteröityä". Firebasen dokumentaatio listaa sen myös [yleisten turvattomien mallien](https://firebase.google.com/docs/rules/insecure-rules) joukossa.

## Miksi omistajatarkistus tarvitsee sekä `resource`- että `request.resource`-kentän?

`resource` on dokumentti sellaisena kuin se on tallennettu. `request.resource` on dokumentti sellaisena kuin se olisi kirjoituksen jälkeen. Sääntö, joka katsoo vain ensimmäistä, tarkistaa kenen rivi on nyt, eikä sano mitään siitä, miksi käyttäjä sen muuttaa.

```
match /orders/{id} {
  allow read, update, delete: if resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null;
}
```

Lukemiset näyttivät oikeilta: A sai `200` omasta tilauksestaan ja `403` B:n tilauksesta. Kirjoitukset eivät. A loi uuden tilauksen, jonka `ownerId` oli B, ja sai `200`. Sitten A muutti oman tilauksensa `ownerId`:n B:ksi, myös `200`, ja B saattoi lukea sen siitä lähtien. Sovelluksessa, jossa tilaukset ovat laskuja tai saldoja, tämä on käyttäjä, joka kirjoittaa jonkun toisen tilille.

Tämä versio kieltäytyi kaikesta:

```
match /orders/{id} {
  allow read, delete: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
  allow create: if request.auth != null
    && request.resource.data.ownerId == request.auth.uid;
  allow update: if request.auth != null
    && resource.data.ownerId == request.auth.uid
    && request.resource.data.ownerId == resource.data.ownerId;
}
```

| A:n pyyntö                      | Tulos |
| ------------------------------- | ----- |
| Lue oma tilaus                  | `200` |
| Lue, päivitä, poista B:n tilaus | `403` |
| Luo tilaus, jonka omistaja on B | `403` |
| Muuta `ownerId` B:ksi           | `403` |

Se salli silti A:n muuttaa oman tilauksensa `total`-kentän, `200`:lla. Jos asiakas ei saa asettaa sitä kenttää, sano, mitä kenttiä hän saa muuttaa:

```
allow update: if request.auth != null
  && resource.data.ownerId == request.auth.uid
  && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['note']);
```

Tällä `total`:n muuttaminen palautti `403` ja `note`:n muuttaminen `200`.

## Miksi kyselyni epäonnistui oikealla säännöllä?

Koska [säännöt eivät ole suodattimia](https://firebase.google.com/docs/firestore/security/rules-query). Yllä olevalla omistajasäännöllä A sai `403`, kun pyysi koko kokoelmaa, vaikka A omistaa 2 tilausta kolmesta. Firestore arvioi kyselyn sen mukaan, mitä se voisi palauttaa, ei sen mukaan, mitä siellä on.

| A:n pyyntö              | Tulos         |
| ----------------------- | ------------- |
| Listaa kaikki tilaukset | `403`         |
| Kysely `ownerId == A`   | `200`, 2 dok. |
| Kysely `ownerId == B`   | `403`         |

Kun lista hajoaa sääntöjen tiukentamisen jälkeen, korjaa kysely. Säännön löysääminen, jotta lista toimisi, on tapa, jolla yllä olevat laajat säännöt syntyvät.

## Voiko laaja match kumota oikean säännön?

Voi, ja tätä etsisin ensimmäisenä projektista, jossa on pitkä sääntötiedosto. Firestore [sallii pyynnön, jos jokin täsmäävä sääntö sallii sen](https://firebase.google.com/docs/firestore/security/rules-structure).

```
match /orders/{id} {
  allow read: if request.auth != null
    && resource.data.ownerId == request.auth.uid;
}
match /{document=**} {
  allow read: if request.auth != null;
}
```

Ensimmäinen lohko näyttää hyvältä omistajasäännöltä. Toisen lohkon ollessa mukana A luki B:n tilauksen `200`:lla, ulkopuolinen listasi kaikki 3 tilausta ja sama ulkopuolinen luki A:n dokumentin kokoelmasta `users`. Ensimmäisessä lohkossa mikään ei varoita. Poista laaja match; ei ole keinoa saada myöhempää sääntöä ottamaan pääsy takaisin.

## Miten käyttäjä tekee itsestään adminin?

Tämä malli tulee vastaan aina, kun roolit asuvat dokumentissa:

```
function isAdmin() {
  return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
}
match /users/{uid} {
  allow read, write: if request.auth.uid == uid;
}
match /orders/{id} {
  allow read: if resource.data.ownerId == request.auth.uid || isAdmin();
}
```

"Käyttäjät voivat muokata omaa profiiliaan" ja "adminit voivat lukea kaikki tilaukset" ovat kumpikin järkeviä. Yhdessä ne ovat oikeuksien korotus.

![Admin yhdellä pyynnöllä. Käyttäjä A, jonka oma users-dokumentti on kirjoitettavissa. Ennen: Käyttäjä A → lue B:n tilaus → 403; Kirjoita oma rooli: Käyttäjä A → role: admin → 200; Jälkeen: Käyttäjä A → lue B:n tilaus → 200.](https://madsalt.tech/charts/fi/firebase-security-rules-examples.svg)

*Kuva 1. Testini Firebase-emulaattorissa (CLI 15.34.2), 3. marraskuuta 2026.*

Korjaus on estää asiakasta kirjoittamasta tuota kenttää. Kun kirjoitussääntöön lisättiin `request.resource.data.role == resource.data.role`, sama pyyntö palautti `403` ja A pysyi ulkona B:n tilauksesta. Palvelimelta asetetut custom claims ovat toinen tie, koska asiakas ei voi muuttaa niitä.

## Kaksi asiaa, jotka osoittautuivat kunnossa oleviksi

Ylätason säännöt eivät vuoda alikokoelmiin. Kun `/orders/{id}` oli `allow read` kaikille kirjautuneille, A luki tilauksen, ja sen alla oleva `/orders/b1/notes/n1` palautti `403`. Se on [dokumentoitu käytös](https://firebase.google.com/docs/firestore/security/rules-structure): säännöt pätevät vain täsmäävällä polulla.

Vieras ei myöskään lipsahda läpi vertailussa puuttuvaan tokeniin. `resource.data.ownerId == request.auth.uid` palautti vieraalle `403` ilman erillistä `request.auth != null` -ehtoakin. Kirjoitan tarkistuksen silti auki, koska silloin sääntö sanoo sen, mitä tarkoittaa.

## Miten omat sääntösi tarkistetaan?

Aja pyyntösi emulaattorissa vieraana, ulkopuolisena ja toisena käyttäjänä. Se paljasti jokaisen yllä olevan tapauksen; sääntöjen lukeminen ei.

Ensimmäiselle läpikäynnille ilmainen [Firebase-sääntötarkistus](https://madsalt.tech/fi/tools/firebase-rules-checker/) lukee liitetyt säännöt koskematta projektiisi. Tässä käytetyistä sääntöjoukoista se liputti testitilan, vain kirjautumista vaativan pääsyn, laajan matchin ja itse kirjoitetun roolin. Se lukee vain tekstiä, joten se ei voi kertoa, millaista datasi on tai onko sääntö julkaistu.

`apiKey` sivullasi ei ole yksinään ongelma. Firebasen web-konfiguraatio on suunnitellusti julkinen, ja ilmainen [secret scanner](https://madsalt.tech/fi/tools/secret-scanner/) merkitsee sen niin, koska sen takana olevaa dataa suojaavat säännöt. Tiedostoilla on taas omat sääntönsä ja vuoto, jota säännöt eivät pysäytä, ja sen testasin artikkelissa Firebase Storagen säännöt.

**Rajoitukset.** Vain Firestore, paikallisessa emulaattorissa, REST:in yli, keksityllä datalla. Realtime Database käyttää toista sääntökieltä, eikä sitä testattu. App Check, joka rajoittaa, mitkä sovellukset saavat kutsua backendiäsi, ei kuulu tähän; se ei korvaa sääntöjä. Tuotanto voi poiketa emulaattorista yksityiskohdissa, joita en tarkistanut.

Etkö ole varma, mitä sääntösi päästävät läpi?

Luen ne mielelläni kanssasi. Ei myyntipuhetta.

[Ota yhteyttä](https://madsalt.tech/fi/#contact)
