Ukončená impersonace + stale formulář přepíše cizí profil
import { Aside } from ‘@astrojs/starlight/components’;
Symptom
Sekce “Symptom”Účet administrátora se opakovaně „sám” přejmenoval na jméno + adresu + bankovní účet různých uživatelů (vždy toho, kterého admin před chvílí spravoval). Žádný útok — interní data-integrity bug. Záludné: i nejstarší zálohy už obsahovaly přepsaná data, takže původní jméno účtu nešlo obnovit.
Root cause
Sekce “Root cause”Admin používá impersonaci (přepne session cookies na cílového uživatele, vlastní tokeny si schová). Endpoint pro úpravu vlastního profilu ukládá slepě podle session z cookie:
const { data: { user } } = await supabase.auth.getUser(); // ← identita z cookieawait supabase.from('profiles').update(updates).eq('id', user.id);Sekvence vedoucí ke korupci:
- Admin impersonuje uživatele → otevře jeho profil → formulář se naplní jeho daty.
- Uloží (správně, session = uživatel).
- Ukončí impersonaci → cookies zpět na admina, redirect pryč.
- Tlačítkem Zpět / přes bfcache se vrátí na stále vyplněný formulář (cizí data v DOM), znovu uloží.
- Teď je session = admin →
eq('id', user.id)zapíše cizí data na adminův účet.
Stejný princip funguje i bez bfcache kdekoli, kde se session přepne mezi načtením formuláře a jeho odesláním.
Fix
Sekce “Fix”Formulář si při načtení zapamatuje, pro které ID byl naplněn, a pošle ho při uložení. Server zápis odmítne, když se identita mezitím změnila:
// klient (load): zapamatuj si vlastníka formulářewindow.__profileLoadedFor = user.id;// klient (save): pošli to s seboubody.expected_user_id = window.__profileLoadedFor;
// server (před update):if (body.expected_user_id && body.expected_user_id !== user.id) { return json({ error: 'Relace se změnila, profil nebyl uložen.' }, 409);}Admin-only editace uživatelů (přes explicitní user_id z těla requestu) byla bezpečná — zranitelný byl jen self-service endpoint, který identitu bral z cookie.
Jak se tomu vyvarovat v jiných systémech
Sekce “Jak se tomu vyvarovat v jiných systémech”- Detection: najdi self-service mutace tvaru
update(...).eq('id', session.user.id), kdeidpochází jen ze session. Zkombinuj s existencí impersonace / „login as user”. - Anti-pattern: formulář načtený v jednom identity-kontextu, odeslaný v jiném; spoléhání čistě na cookie session u zápisu, který nese předvyplněná data.
- Lepší přístup: každý mutate request, který nese předvyplněná data, ať obsahuje očekávané ID vlastníka a server ho ověří proti session (optimistic-concurrency styl). Po ukončení impersonace navíc tvrdě reloadni a invaliduj bfcache (
Cache-Control: no-storena chráněných stránkách).
Sister bugs / související
Sekce “Sister bugs / související”Příbuzné pasti impersonace: obnova session po impersonaci banovaného uživatele (getUser selže), úniky identity mezi tabem s impersonací a běžným tabem (cookies jsou sdílené per-doména).