Firebase-gulvet ga etter — når tenantsitteren bor i én samling
16 min lesingVis som Markdown
pentest · firebase · firestore · cloud-run · api-security · multi-tenant · ai · security
FIKTIVT: Alle firmanavn, personnavn, domener og e‑postadresser i dette innlegget er oppdiktet. Eventuell likhet med ekte virksomheter eller personer er tilfeldig. Detaljer fra reelle oppdrag er sanert.
Denne gangen var målet ikke å knekke inn — det var å bevise hva man kunne gjøre
etter å ha kommet inn. Skattebureauet Fjordlys AS (fjordlysas.no) kjører en
krypto- og skatteberegningstjeneste bygget på Firebase Auth, Firestore, en
Next.js-app på Vercel og en FastAPI-backend på Google Cloud Run. Alt av
klassisk, moderne sky. Og den moderne stacken leverte to av de mer interessante
funnene jeg har hatt denne sesongen: en Firestore-samling som lot seg dumpe
rett ut, og et dokument-API som godtok hvem som helst med et gyldig token —
for hvilken som helst kunde.
Dette er en lang skriveprosess. Det er ikke fordi selve angrepet var spesielt avansert, men fordi hvert funn fortjener å bli fulgt helt ut: forutsetningene, den eksakte forespørselen, den rå responsen, den negative kontrollen, og mitigeringen. Jeg tror det er der verdien ligger — ikke i lister av alarmer, men i kjeden som beviser hver av dem.
Kontekst
SoW-en ba om tre ting: eskalering til systemadministrator, uttrekk av
kundedata, og vurdering av backend-API-ene. Klienten, ved daglig leder Ola
Hansen ([email protected]), hadde uttrykkelig godkjent full-send og
aggressiv metode. Scope var frontenden på www.fjordlysas.no, et parallelt
Vercel-utviklerdomene, og et Cloud Run-API.
Jeg kjører denne typen oppdrag med AI i loopen: jeg starter med recon og kaster en strøm av hypoteser mot målet, agentene går parallelt på endepunkter og auth-flipper, og jeg tar vurderingene — som denne gangen ble flere og vanskeligere enn jeg liker. Stacken er dessuten svært snakkende: med et Firebase-basert produkt sitter hele datamodellen i praksis i klienten, og buntene spytter ut alt du trenger for å kjøre mot samme backend som appen selv gjør.
Oppsett og vektløftingen før angrepet
Første time var ren kartlegging. AI-en kjørte subdomain-enumerasjon med
subfinder og httpx, og nuclei mot infrastrukturen:
nuclei -u https://www.fjordlysas.no -t cves/ -severity critical,high -rl 50Nul treff mot 4 222 CVE-maler. Det er et funn i seg selv —
infrastrukturlaget var oppdatert. Men det viktigste var noe enklere:
det paralelle rå‑Vercel‑domenet (i denne teksten raw-deploy.vercel.app)
svarte uten WAF-bonnen Cloudflare setter opp foran hoveddomenet. Det ga meg
en trafikk-skanse for resten av oppdraget — ikke et funn i seg selv, men en
kondisjon som gjorde alt annet lettere.
Frontendbuntene lastet jeg ned og la i et lokalt arbeidsområde. Det er vanlig praksis, men fortjener en nevnning fordi alt det virkelige arbeidet senere kom fra å lese dem ordentlig:
grep -ohR "/api/[a-z][a-z0-9/_-]*" bunten/ | sort -u > endpoints.txt
wc -l endpoints.txt # 164 endepunkter164 API-ruter, ekstrahert fra Vite/Next.js-bunter med en AI-generert
rg-pipeline. Det er tjenestens hele API-overflate, plukket fra klienten
som en leser kunne gjort over en lunsj.
Vekten av en trauskanse: WAF-bypass-ruten
Før selve funnene, en liten egen historie som gjorde hele oppdraget enklere — og som er verdt å formidle fordi det er et mønster jeg ser gjentatte ganger på Vercel-baserte produkter.
Hoveddomenet www.fjordlysas.no lå bak Cloudflare. Cloudflare setter en
Vercel Security Checkpoint-utfordring på bot-trafikk, og det gjør
skannere og automatiserte forespørsler tungvindt: man må verifisere seg,
cookies rullerer, og nuclei-sweeps blir tregere og støyere enn de trenger
å være. Det er tjenesten så vidt jeg forstår bevisst — men den har en blind
flekk: den rå Vercel-deploy-URL-en.
Hver Vercel-deploy kan nås direkte via <prosjekt>.vercel.app, uten
Cloudflare foran. DNS-en peker mot Vercel, men ingenting hindrer en i å
snakke med Vercel-endepunktet direkte — med mindre prosjekteier har
konfigurert domain-verifisering som avviser trafikk som ikke kommer via
kundens eget domenenavn. Det hadde ikke blitt gjort her:
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
https://www.fjordlysas.no/api/health # 403 (Cloudflare challenge)
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
https://raw-deploy.vercel.app/api/health # 200 {"status":"ok"}Samme app, samme backend, samme API — men én rute gjennom WAF-en og én
uten. Resten av oppdraget kjørte jeg mot Vercel-domenet med en standard
Chrome UA-streng (Mozilla/5.0 Chrome/151.0.0.0; en annen UA ga
403 med Cloudflare-feilkode 1010 på enkelte ruter — verdt å notere
hvis du replikerer).
Det er ikke et sårbarthetsfunn i seg selv — appen er lik, og alt som er tilgjengelig bak Vercel-URL-en er også tilgjengelig gjennom hoved domenet for en ekte bruker. Men det er en operasjonell svakhet: WAF-en, som er en del av forsvarsoverflaten, kan omgås fullstendig, og ratelimitregler som er konfigurert i Cloudflare gjelder kun hoveddomenet. Jeg rapporterte det som en anbefaling ("lay Vercel-deploys bak Cloudflare i front, eller la Vercel avvise Host-headere som ikke matcher kundens domene") snarere enn et funn, fordi eksploitverdien er indirekte. Low.
Oppsette probe-kontoen
Firebase API-nøkkelen sto i klartekst i hovedbunden (klassisk Firebase-mønster; nøkkelen er ikke en hemmelighet, men den er nødvling for at REST-API-ene skal virke). Med den registrerte jeg en prøvebruker via Googles identitytoolkit:
POST /v1/accounts:signInWithPassword?key=<FIREBASE_API_KEY>
Host: identitytoolkit.googleapis.com
{"email":"[email protected]","password":"<REDACTED>","returnSecureToken":true}{
"localId": "1111111111111111111111111111",
"idToken": "<REDACTED — 974 bytes>",
"refreshToken": "<REDACTED>",
"expiresIn": "3600"
}Verifiserte e-posten via accounts:sendOobCode mot en postkasse jeg kontrollerer.
Det er den tilstanden alt det videre forutsetter: et gyldig Firebase JWT
for en alminnelig, nyregistrert kundebruker. Ingen roller, ingen custom
claims — bare standardtokenet alle kunder går rundt med.
Det jeg fant
Fire funn holdt seg til slutt. Tre av dem er egentlig én historie om Firestore-regler og manglende tenant-filter i en backend, og den fjerde er et postendepunkt som lar deg sende e-post fra firmaets eget domene.
Funn 1: 974 brukerprofiler i klartekst
Firestore-sikkerhetsreglene for users-samlingen tillot lesing av hele
samlingen for enhver innlogget bruker. En enkel list-operasjon via
Firestore REST API med min egen Firebase-token returnerte paginert
brukerdata — 974 dokumenter i klartekst: e‑post, navn, kunde-ID-er
(CUST-<timestamp>-format), kontonumre, roller og opprettelsestidspunkter.
GET /v1/projects/fjordlys-frontend/databases/(default)/documents/users?pageSize=5
Host: firestore.googleapis.com
Authorization: Bearer <FIREBASE_JWT>{
"documents": [
{
"name": "projects/fjordlys-frontend/databases/(default)/documents/users/1111...",
"fields": {
"email": {"stringValue": "[email protected]"},
"customer_id": {"stringValue": "CUST-1765897468504"},
"role": {"stringValue": "company_admin"},
"approved": {"booleanValue": true}
}
}
],
"nextPageToken": "<REDACTED>"
}Det er ikke teknisk komplisert — det er bare en regel som glemte at
list og get er to ulike operasjoner. Jeg fikk AI-en til å skrive et
lite paginerings-script som dumpet hele samlingen; det tok under tre minutter.
High.
Dette er det klassiske Firebase-feilgrepet: sikkerhetsregler skrives for
"kan lese sitt eget dokument", men list-operasjonen arver ikke
get-regelen automatisk. En regel som kun sjekker
request.auth.uid == resource.id blokkerer lesing av andres dokumenter
— men listingen av hele samlingen må avslås separat, og det ble den ikke.
Funn 2: Cross-tenant filmetadata via dokument-API-et
FastAPI-backend på Cloud Run eksponerte /openapi.json åpent. Elleve
endepunkter, og to av dem tok en customer_id-parameter:
GET /api/documents?customer_id=0592-5351-8860-8859
Host: api.fjordlys-internal.europe-west1.run.app
Authorization: Bearer <FIREBASE_JWT>Responsen var filmetadata for en kunde jeg aldri hadde hørt om — filnavn, GCS-sti, filstørrelse, opplastingstidspunkt og behandlingsstatus, for multiple platformer (kryptobørser, tradingsplattformer). Det er kundemappestrukturen i et sky-lagringsbøtte, hentet ut med min alminnelige kundebrukers token.
{
"files": [
{
"name": "20251218_212910_transactions.xlsx",
"path": "0592-5351-8860-8859/coinbase/20251218_212910_transactions.xlsx",
"size": 76475,
"customerId": "0592-5351-8860-8859",
"platformId": "coinbase"
}
]
}Negativ kontroll først: med et ukjent kunde-ID returnerte samme endepunkt
200 med en tom liste — ikke 403, ikke 404. Det er ikke noe som beviser
lekkasje i seg selv, det beviser bare at endepunktet ikke avgrenser på
eierskap. PoC-en under viser begge sider.
High.
Funn 3: Cross-tenant filskriving
Samme endepunktfamilie hadde en upload-rute som tok multipart-form med
customer_id og fil:
curl -X POST https://api.fjordlys-internal.europe-west1.run.app/api/documents/upload \
-H "Authorization: Bearer <FIREBASE_JWT>" \
-F "customer_id=0592-5351-8860-8859" \
-F "platform_id=coinbase" \
-F "file=@/tmp/poc.csv"{
"id": "13b4e903-c7c1-4281-94ca-327798e39100",
"filename": "poc.csv",
"gcs_path": "gs://fjordlys-customer-documents/0592-5351-8860-8859/coinbase/20260829_211044_poc.csv"
}Etterfulgt av en liste-operasjon som bekreftet at filen nå lå i kundemappen, ved siden av kundens eksisterende transaksjonsfiler. Det er skriving til en fremmed kundes datamappe, med et token som ikke inneholder den kunden, og uten at noen autorisasjonssjekk grep inn. High.
Funn 4: E-postutsending fra firmaets domene
POST /api/send-email på Next.js-serveren sendte en testmelding via firmaets
e-postleverandør, fra domenet fjordlysas.no. Rate-limit var 79 per dag, og
alle utsendelser ble auto-kopiert til en intern adresse (som betyr at mottakeren
av kopien ser at en test har gått — men selve utsendingen kom fra
den offisielle domenet, fra en vanlig kundebrukers token).
Medium.
PoC-en for dette var enkel, men jeg vil vise den likevel — e-postutsending er en av de funntypene der en enkelt respons forteller deg alt:
Forutsetninger: Firebase JWT fra probe-kontoen. Utsendingen ble kjørt etter avtale i SoW, mot en postkasse jeg selv kontrollerer.
POST /api/send-email HTTP/1.1
Host: raw-deploy.vercel.app
Authorization: Bearer <FIREBASE_JWT>
Content-Type: application/json
{"to":"[email protected]","subject":"PoC","body":"Test fra pentest-probe"}HTTP/1.1 200 OK
Content-Type: application/json
{"ok":true,"remainingToday":78}Tolkning: 200 pluss at meldingen faktisk ankom min testpostkasse,
med From: på fjordlysas.no, er beviset. Auto-kopien til den interne
adressen var synlig i mine headers — det er en implementasjonsdetalj
som kanskje var ment som en varslingsmekanisme, men den gjør også at
den som mottar meldingen kan se at en test har gått. Det gir
sporbarhet, men hindrer ikke misbruk: en angriper kan sende meldinger
fra det offisielle domenet til hvem som helst, med et vanlig kunde
token, 79 ganger per dag.
En negativ kontroll her var å sende med manglende felter — den ga en ryddig valideringsfeil, noe som viser at validering eksisterer på skjema-nivå men ikke på autorisasjonsnivå:
HTTP/1.1 400 Bad Request
{"message":"Mangler mottaker"}Mitigering: Krav om admin-rolle eller minimum en eierskapssjekk (kun send til egen registrerte e-post, med tydelig "systemmelding fra testmiljø"-markering). Medium.
Funn 5: Kundesekvens-sweepen som ikke ga treff — og hvorfor det var nyttig
En metodemessig sidegrein jeg regner som et delresultat. Jeg trengte
flere kunde-ID-er for å vise at Funn 2 var systematisk, ikke en
tilfeldighet. Kunde-ID-ene i Firestore-dumpen (CUST-<timestamp>-format)
matchet ikke formatet Cloud Run-API-et forventet
(0592-5351-8860-8859-format — fire blokker på fire siffer). Det er
tydeligvis to ulike identifikatorsystemer i samme tjeneste.
Jeg fikk en agent til å generere og teste ~1 500 ID-varianter mot
/api/documents med parallell utførelse:
from concurrent.futures import ThreadPoolExecutor
def probe(cid):
req = urllib.request.Request(
f"{BASE}/api/documents?customer_id={cid}",
headers=UA, method="GET")
d = json.loads(urllib.request.urlopen(req, timeout=8).read())
return (cid, len(d)) if isinstance(d, list) and d else None
with ThreadPoolExecutor(20) as ex:
for hit in ex.map(probe, test_ids):
if hit:
print(hit)Null treff på 1 500 forsøk. Det er en negativ kontroll av samme slag som det jeg forventet å finne — den forteller at kunde-ID-ene ikke er gjetbare i praksis, og at det reelle angrepet ikke er "brute-force deg til en kundefolder" men "vite én kunde-ID og lese den". Jeg fikk vite én kunde-ID fra Firestore-dumpen (Funn 1), og det var nok. De to funnene kobler seg altså til hverandre: samlingenlekkasjen leverer materiellet (kunde-ID-er), og dokumentAPI-et leverer lekkasjen (metadata + skriving).
Det er et godt eksempel på en teknikk jeg bruker bevisst: en negativ sweep som beviser at angrepsflaten er smal, er like verdifull som en positiv som viser at den er bred. Uten sweepen ville en leser (eller klienten) kunne tro at cross-tenant-funnet bare var et problem hvis man alt kjente til ID-en — det er ikke tilfelle.
PoC / Reproduksjon
For Funn 2–3 kjører hele kjeden slik:
Forutsetninger: innlogget som nyregistrert kundebruker
([email protected], Firebase JWT i token.txt). Kundefolder
0592-5351-8860-8859 tilhører en annen bruker — jeg har aldri hatt
tilgang til den, og fant ID-en i Firestore-dumpen fra Funn 1.
- Registrer bruker og hent Firebase JWT:
curl -s "https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=<FIREBASE_API_KEY>" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"<REDACTED>","returnSecureToken":true}'- List fremmed kundes filer:
GET /api/documents?customer_id=0592-5351-8860-8859 HTTP/1.1
Host: api.fjordlys-internal.europe-west1.run.app
Authorization: Bearer <FIREBASE_JWT>HTTP/1.1 200 OK
Content-Type: application/json
[{"id":"c6978901-...","filename":"20251218_212910_transactions.xlsx",
"gcs_path":"gs://fjordlys-customer-documents/0592-5351-8860-8859/coinbase/20251218_212910_transactions.xlsx"}]- Last opp en fil i samme mappe (bevis for skriving):
POST /api/documents/upload HTTP/1.1
Host: api.fjordlys-internal.europe-west1.run.app
Authorization: Bearer <FIREBASE_JWT>
Content-Type: multipart/form-data; boundary=...
--...
Content-Disposition: form-data; name="customer_id"
0592-5351-8860-8859
--...
Content-Disposition: form-data; name="file"; filename="poc.csv"
Content-Type: text/csv
test,data
--...--HTTP/1.1 200 OK
{"id":"13b4e903-...","gcs_path":"gs://fjordlys-customer-documents/0592-5351-8860-8859/coinbase/20260829_211044_poc.csv"}- Negativ kontroll — prøv å list dokumenter for en kunde-ID som ikke finnes (tilfeldig generert):
GET /api/documents?customer_id=1234-5678-9012-3456
Authorization: Bearer <FIREBASE_JWT>HTTP/1.1 200 OK
[]Tolkning: tom liste — ikke 403 eller 404. Endepunktet oppfører seg
likt for en kunde jeg eier, en jeg ikke gjør, og en som ikke finnes. Det er
det som gjør Funn 2–3 til et strukturelt problem og ikke en engangsfeil:
autorisasjonssjekken mangler, den er ikke bare feilkonfigurert for én kunde.
High.
Mitigering: Cloud Run-endepunktet må validere at customer_id
svarer til en kunde som kallerens Firebase-bruker faktisk eier
(foreks. via users/{uid}.customer_ids), og returnere 404 ved avvik
for ikke å bekrefte eksistens:
@router.get("/documents")
-async def list_documents(customer_id: str, user=Depends(get_current_user)):
- return await storage.list_files(f"{customer_id}/")
+async def list_documents(customer_id: str, user=Depends(get_current_user)):
+ if customer_id not in user.customer_ids:
+ raise HTTPException(404)
+ return await storage.list_files(f"{customer_id}/")Samme filter trengs på upload-ruten, og der bør opplastingen også logges med hvem som lastet opp.
Fire funn til — og hvorfor de ikke holdt
En god del av det jeg oppdaget underveis så ut som store funn, men ble nedgradert etter verifisering. Det er like mye å lære av dem, og de forteller noe om forskjellen mellom "plausible funn" og "kontrollerte primitiver" som jeg prøver å holde strengt i mine rapporter.
Administratorportalen jeg aldri kom inn i
JS-buntene viste hardkodede e-postadresser (typisk admin@…-mønster) som
klienten sjekket for å rendre en admin-dashboard. Så tiltalende — skriv
din egen e-post inn i brukerdokumentet, og klienten gir deg UI-en.
Men serveren validerte ikke klientens sjekk. Den validerte med
Firebase custom claims (crmExternal: true) — som settes kun via
Admin SDK og verifiseres serverside i JWT-en. Jeg skrev
role: "sysadmin" til mitt eget Firestore-dokument, oppdaterte
crmExternal: true, og testet hele admin-flaten igjen. Alle
admin-endepunktene fortsatte å svare 403.
Negativ kontroll (i praksis): jeg verifiserte via accounts:lookup
at tokenet ikke inneholdt custom claims (customAttributes: none),
og bekreftet at det var JWT-en, ikke Firestore-dokumentet, som var
den server-side kilden til sannhet. Admin-laget holdt — rapporterte
det som en positiv vurdering, ikke et funn.
Cloud Run-registreringen som ikke gav tilgang
Backend-API-et hadde et eget brukersystem med /api/auth/register
som aksepterte min registrering og returnerte et UUID-token. Så ble
samme token avvist av samme backend én fetch senere:
GET /api/auth/me HTTP/1.1
Authorization: Bearer <BACKEND_TOKEN>
HTTP/1.1 401 Unauthorized
{"detail": "Invalid authentication credentials"}Mens Firebase JWT-er fungerer mot dokument-endepunktene på samme backend. Det er et funn i seg selv — to autoriseringssystemer som ikke er i samsvar — men ingen innbrudd, og det er verdt å nevne som et varsel til klienten: to identitetslag som ikke er koordinert er en kilder til fremtidige hull.
External-upload-secret-en
Et uautentisert token-utvekslingsendepunkt (/api/crm/external-upload/exchange)
returnerte et Firebase custom token ved gyldig customerId + secret,
og det custom-tokenet inneholdt crmExternal: true — altså den samme
claim-en som admin-endepunktene sjekker. Det er en riktig vei til
økt tilgang, hvis man kjenner en gyldig secret. Men secret-en viste
seg å være en UUID v4 (2^122 muligheter), og jeg fant ingen way å
enumerere den. Droppet brute-force, rapporterte mekanismen som et
risikomønster i stedet — særlig fordi alle kunder så ut til å mangle
provisionerte secrets, noe som betyr at angrepet i praksis er
teoretisk inntil noen aktiverer det.
GCS-bøtten jeg kunne navngi, men ikke lese
Med Firebase JWT returnerte både XML- og JSON-API-et til Google
Cloud Storage 401 Invalid Credentials — Googles storage-API
forventer OAuth-token, ikke Firebase-ID-token. Filinnholdet kunne
jeg ikke hente ut byte for byte uten en annen auth-vei; kun metadata
og skriving var tilgjengelig via backend-API-et. Jeg prøvde også
Firebase Storage API — der fikk jeg en annen feil, at bøtten ikke
var konfigurert for Firebase Storage, noe som antyder at tjenesten
bruker rå-GCS med tjenestekonto-nøkler på backend-siden (som forventet).
Det var en fordel for klienten: filinnholdet er ikke lesbar for
en alminnelig Firebase-bruker selv om metadataene er det.
Verktøy og hvem som kjørte hva
- Recon og subdomain-enumerasjon: AI-agent med
subfinderoghttpx -silent -title. Jeg satte scope-filen. - nuclei v3 (4 222 CVE-templates): AI-kjør mot både hoveddomenet og rå-Vercel-utpakkingen. Null treff — som var poenget: ingen kjente CVE-er å bygge videre på.
- Frontend-bunt-analyse: Jeg lastet ned
index-*.jsog chunk-filer manuelt; en AI-agent ekstraherte 164 API-ruter medrg-mønsteret over. - Firestore-dumpen: AI-generert Python-script med
urllib, paginert mot Firestore REST API med min egen Firebase JWT. Jeg satte ratelimit og verifiserte at jeg ikke skrev noe. - Upload-PoC: Manuell
curlfra min maskin, mot en kundefolder avtalt i SoW. Filinnholdet var en to-linjers CSV med en teststreng. - Negativ kontroll: Manuell — det er vurderingen som avgjør om responsen beviser noe, og den vil jeg ikke delegere.
- Brute-force av tenant- og kunde-ID-er: AI-agent kjørte en
parallellisert
ThreadPoolExecutor-sweep mot/api/documents?customer_id=Xmed ~1 500 genererte ID-er. Null treff — som var nyttig: det betydde at kunde-ID-ene ikke var sekvensielle eller gjetbare, så fundet Funn 2 er ikke et enumerasjonproblem i praksis, men et autorisasjonsproblem.
Mitigering
Fire konkrete forslag, i prioritert rekkefølge:
- Firestore-regler: endre regelverket slik at
users-samlingen tillatergetpårequest.auth.uid == resource.id, men ikkelistfor alminnelige brukere. Dette alene lukker 974-profil-lekkasjen. High. - Tenant-filter på dokument-API-et: legg til et predikat på både
listoguploadsom sjekkercustomer_idmot kallerenscustomer_ids, og returner404ved avvik (ikke403). High. - E-postutsending:
/api/send-emailbør kreve admin-rollen eller i det minste MFA-gated tilgang, ikke bare et gyldig Firebase-token. Medium. - Generelt: gjennomgå alle backend-endepunkter som tar en kunde-ID-parameter. To av elleve i OpenAPI-spec-en manglet eierskapssjekk — det er et mønster, ikke en tilfeldighet.
Etter Firestore-regelendringen skal en nyregistrert brukers
list-forsøk på users-samlingen svare 403 PERMISSION_DENIED
— ikke bare en tom liste. Test det regressivt etter deploy.
Hva jeg lærte
- Firestore-regler feiler på
list, ikkeget. Alle tester av "kan jeg lese mitt eget dokument" passerer. Først når du listar samlingen ser du at regelen bare dekker enkeltdokumenttilfelle. - Cross-tenant i fil-API-er ser ofte ut som "tom liste" i stedet
for
403. Den tomme responsen er lettere å overse, og den er det som gjør bruddet systematisk. - Custom claims er ikke synlige i Firestore. Jeg prøvde å eskalere til sysadmin ved å skrive en rolle til mitt eget Firestore-dokument; serveren ignorerte det fullstendig. Custom claims settes kun via Admin SDK og verifiseres i JWT-en — den modellen holdt, og det er verdt å si også.
- En moderne Firebase-stack er utrolig informativ å angripe. Bunter, REST-endepunkter og autoriseringsregler er alle lesbare. En produkteier som har et slikt oppsett bør teste regler og endepunkter systematisk, ikke bare web-flaten.
- AI-agenter produserer plausible funn fort — og plausible feilgrep like fort. Verdien ligger i verifiseringen. Jeg så minst to funn denne runden som så "kategorisk gale" ut i en agent-rapport, og som falt fra hverandre når jeg kjørte dem manuelt med en negativ kontroll. Rapporten er langt kortere enn agentenes funnlister, og det er med vilje.
Veien videre
Funnene er rapportert og fikseansvarlig er varslet. Jeg jobber videre med to tråder:
- Klientbuntanalysen er skalerbar — jeg jobber med et lite verktøy som trekker ut API-ruter og Firestore-samlingnavn fra en produksjonsbunt automatisk, slik at denne typen kartlegging kan kjøres i starten av ethvert Firebase-basert oppdrag.
- For kunder med Firebase-stack: en liten sjekkliste ("kan en nybruker
list-e minusers-samlingen? har hvert endepunkt med en kunde-ID-parameter en eierskapssjekk?") som jeg regner med blir en fast del av mine oppdrag fremover.
Dessuten: custom-claims-modellen som holdt her er verdt en egen artikkel. Det er den eneste autoriseringsmekanismen jeg møtte i dette oppdraget som jeg ikke kom forbi, og grunnen er verdt å skrive ut eksplisitt.