sql >> Databáze >  >> RDS >> PostgreSQL

Jak fungují pohledy PostgreSQL security_barrier?

Možná jste viděli přidanou podporu pro security_barrier zobrazení v PostgreSQL 9.2. Zkoumal jsem tento kód s ohledem na přidání podpory automatických aktualizací pro ně jako součást postupující práce na zabezpečení na úrovni řádků pro projekt AXLE a řekl jsem si, že bych využil příležitosti a vysvětlil, jak fungují.

Robert už vysvětlil, proč jsou užitečné a před čím chrání. (Ukazuje se, že je to také diskutováno v tom, co je nového v 9.2). Nyní chci přejít k jak pracují a diskutují o tom, jak security_barrier pohledy interagují s automaticky aktualizovatelnými pohledy.

Normální zobrazení

Normální jednoduchý pohled je rozšířen způsobem podobným makru jako poddotaz, který je pak obvykle optimalizován vytažením jeho predikátu a jeho připojením ke kvalifikacím obsahujícího dotazu. To by mohlo dávat větší smysl s příkladem. Daná tabulka:

CREATE TABLE t AS SELECT n, 'secret'||n AS secret FROM generate_series(1,20) n;

a zobrazit:

CREATE VIEW t_odd AS SELECT n, secret FROM t WHERE n % 2 = 1;

dotaz jako:

SELECT * FROM t_odd WHERE n < 4

je rozbalen pohledem uvnitř přepisovače dotazů do reprezentace stromu analýzy dotazu jako:

SELECT * FROM (SELECT * FROM t WHERE n % 2 = 1) t_odd WHERE n < 4

který pak optimalizátor sloučí do jednoprůchodového dotazu odstraněním poddotazu a připojením WHERE klauzule do vnějšího dotazu, produkující:

SELECT * FROM t t_odd WHERE (n % 2 = 1) AND (n < 4)

I když zprostředkující dotazy nevidíte přímo a nikdy neexistují jako skutečné SQL, můžete tento proces pozorovat povolením debug_print_parse =on , debug_print_rewritten =on a debug_print_plan =on v postgresql.conf . Nebudu zde reprodukovat analyzovat a plánovat stromy, protože jsou poměrně velké a snadno se generují na základě výše uvedených příkladů.

Problém s používáním pohledů pro zabezpečení

Možná si myslíte, že když někomu udělíte přístup k pohledu, aniž byste mu udělili přístup k podkladové tabulce, přestalo by se mu zobrazovat sudé řádky. Zpočátku to vypadá, že je to pravda:

regress=> SELECT * FROM t_odd WHERE n < 4;
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

ale když se podíváte na plán, můžete vidět potenciální problém:

regress=> EXPLAIN SELECT * FROM t_odd WHERE n < 4;
                    QUERY PLAN                     
---------------------------------------------------
 Seq Scan on t  (cost=0.00..31.53 rows=2 width=36)
   Filter: ((n < 4) AND ((n % 2) = 1))
(2 rows)

Poddotaz zobrazení byl již optimalizován a kvalifikátory zobrazení byly připojeny přímo k vnějšímu dotazu.

V SQL AND a NEBO nejsou objednány. Optimalizátor/exekutor může volně spouštět kteroukoli větev, o které si myslí, že je pravděpodobnější, že jim dá rychlou odpověď a případně jim umožní vyhnout se spouštění ostatních větví. Pokud si tedy plánovač myslí, že n <4 je mnohem rychlejší než n % 2 =1 to vyhodnotí jako první. Zdá se neškodné, že? Zkuste:

regress=> CREATE OR REPLACE FUNCTION f_leak(text) RETURNS boolean AS $$
BEGIN
  RAISE NOTICE 'Secret is: %',$1;
  RETURN true;
END;
$$ COST 1 LANGUAGE plpgsql;

regress=> SELECT * FROM t_odd WHERE f_leak(secret) AND n < 4;
NOTICE:  Secret is: secret1
NOTICE:  Secret is: secret2
NOTICE:  Secret is: secret3
NOTICE:  Secret is: secret4
NOTICE:  Secret is: secret5
NOTICE:  Secret is: secret6
NOTICE:  Secret is: secret7
NOTICE:  Secret is: secret8
NOTICE:  Secret is: secret9
NOTICE:  Secret is: secret10
NOTICE:  Secret is: secret11
NOTICE:  Secret is: secret12
NOTICE:  Secret is: secret13
NOTICE:  Secret is: secret14
NOTICE:  Secret is: secret15
NOTICE:  Secret is: secret16
NOTICE:  Secret is: secret17
NOTICE:  Secret is: secret18
NOTICE:  Secret is: secret19
NOTICE:  Secret is: secret20
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

regress=> EXPLAIN SELECT * FROM t_odd WHERE f_leak(secret) AND n < 4;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on t  (cost=0.00..34.60 rows=1 width=36)
   Filter: (f_leak(secret) AND (n < 4) AND ((n % 2) = 1))
(2 rows)

Jejda! Jak vidíte, uživatelsky dodaná predikátová funkce byla považována za levnější na spuštění než ostatní testy, takže prošla každým řádkem dříve, než ji predikát pohledu vyloučil. Škodlivá funkce by mohla použít stejný trik ke zkopírování řádku.

bezpečnostní_bariéra zobrazení

bezpečnostní_bariéra pohledy to opravují tím, že vynutíte, aby byly nejprve provedeny kvalifikátory na pohledu, než se spustí jakékoli uživatelem dodané kvalifikátory. Namísto rozšíření pohledu a připojení jakýchkoli kvalifikátorů pohledu k vnějšímu dotazu nahrazují odkaz na pohled poddotazem. Tento dílčí dotaz má security_barrier příznak nastavený na jeho položce tabulky rozsahů, který říká optimalizátoru, že by neměl poddotaz zplošťovat ani do něj vkládat podmínky vnějšího dotazu, jako by tomu bylo u normálního poddotazu.

Takže s pohledem na bezpečnostní bariéru:

CREATE VIEW t_odd_sb WITH (security_barrier) AS SELECT n, secret FROM t WHERE n % 2 = 1;

dostáváme:

regress=> SELECT * FROM t_odd_sb WHERE f_leak(secret) AND n < 4;
NOTICE:  Secret is: secret1
NOTICE:  Secret is: secret3
 n | secret  
---+---------
 1 | secret1
 3 | secret3
(2 rows)

regress=> EXPLAIN SELECT * FROM t_odd_sb WHERE f_leak(secret) AND n < 4;
                          QUERY PLAN                           
---------------------------------------------------------------
 Subquery Scan on t_odd_sb  (cost=0.00..31.55 rows=1 width=36)
   Filter: f_leak(t_odd_sb.secret)
   ->  Seq Scan on t  (cost=0.00..31.53 rows=2 width=36)
         Filter: ((n < 4) AND ((n % 2) = 1))
(4 rows)

Plán dotazů by vám měl říct, co se děje, i když ve výstupu vysvětlení nezobrazuje atribut bezpečnostní bariéry. Vnořený poddotaz vynutí skenování na t s kvalifikátorem pohledu, pak se funkce dodaná uživatelem spustí na základě výsledku dílčího dotazu.

Ale. Počkejte chvilku. Proč je uživatelsky zadaný predikát n <4 také uvnitř poddotazu? Není to potenciální bezpečnostní díra? Pokud n <4 je posunuto dolů, proč není f_leak(secret) ?

TESNOSTNÍ operátory a funkce

Vysvětlení pro to je, že < operátor je označen LEAKPROOF . Tento atribut označuje, že operátor nebo funkce jsou důvěryhodné, aby neunikly informace, takže je lze bezpečně potlačit přes security_barrier pohledy. Z pochopitelných důvodů nemůžete nastavit LEAKPROOF jako běžný uživatel:

regress=> ALTER FUNCTION f_leak(text)  LEAKPROOF;
ERROR:  only superuser can define a leakproof function

a superuživatel si už může dělat, co chce, takže se nemusí uchylovat k trikům s funkcemi, které prosakují informace, aby se dostaly přes bezpečnostní bariéru.

Proč nemůžete aktualizovat security_barrier zobrazení

Jednoduchá zobrazení v PostgreSQL 9.3 jsou automaticky aktualizovatelná, ale security_barrier pohledy nejsou považovány za „jednoduché“. Je tomu tak proto, že aktualizace pohledů závisí na schopnosti srovnat poddotaz pohledu a proměnit aktualizaci v jednoduchou aktualizaci v tabulce. Celý smysl security_barrier zobrazení je zabránit to zploštění. AKTUALIZACE nemůže aktuálně pracovat na poddotazu přímo, takže PostgreSQL odmítne jakýkoli pokus o aktualizaci security_barrier zobrazit:

regress=> UPDATE t_odd SET secret = 'secret_haha'||n;
UPDATE 10
regress=> UPDATE t_odd_sb SET secret = 'secret_haha'||n;
ERROR:  cannot update view "t_odd_sb"
DETAIL:  Security-barrier views are not automatically updatable.
HINT:  To enable updating the view, provide an INSTEAD OF UPDATE trigger or an unconditional ON UPDATE DO INSTEAD rule.

Právě toto omezení mám zájem zrušit jako součást práce na pokroku v zabezpečení na úrovni řádků pro projekt AXLE. Kohei KaiGai odvedl skvělou práci se zabezpečením na úrovni řádků a funkcemi jako security_barrier a TESNOSTNÍ z velké části vznikly z jeho práce na přidání zabezpečení na úrovni řádků do PostgreSQL. Další výzvou je, jak se vypořádat s aktualizacemi bezpečnostní bariéry bezpečně a způsobem, který bude udržitelný do budoucna.

Proč dílčí dotazy?

Možná se divíte, proč k tomu musíme používat poddotazy. Udělal jsem. Zkrácená verze je, že nemusíme, ale pokud nepoužíváme poddotazy, musíme místo toho vytvořit nové varianty AND citlivé na pořadí a NEBO operátory a naučit optimalizátor, že mezi nimi nemůže přesouvat podmínky. Vzhledem k tomu, že pohledy jsou již rozšířeny jako poddotazy, je mnohem méně komplikované pouze označit poddotazy jako ploty, které blokují stahování/posouvání.

V PostgreSQL však již došlo ke zkratování objednané operace – CASE . Problém s použitím CASE že ne operace mohou být přesunuty přes hranici CASE , dokonce i TĚSNÉ jedničky. Optimalizátor také nemůže rozhodovat o použití indexu na základě výrazů uvnitř CASE období. Pokud bychom tedy použili CASE jak jsem se ptal na -hackery, nikdy jsme nemohli použít index ke splnění kvalifikátoru dodaného uživatelem.

V kódu

bezpečnostní_bariéra podpora byla přidána v 0e4611c0234d89e288a53351f775c59522baed7c . Byl vylepšen o nepropustnou podporu v cd30728fb2ed7c367d545fc14ab850b5fa2a4850 . Poděkování se objeví v poznámkách k odevzdání. Díky všem zúčastněným.

Obrázek funkce na přední stránce je Security Barrier od Craiga A. Rodwaye, na Flikr

Výzkum vedoucí k těmto výsledkům byl financován ze sedmého rámcového programu Evropské unie (FP7/2007-2013) na základě grantové dohody č. 318633


  1. Jak funguje WEEKDAY() v MariaDB

  2. Získání minimálně dvou hodnot v SQL

  3. Chyba při použití kromě v dotazu

  4. Co může způsobit změnu Oracle ROWID?