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

Jak bezpečný je formát format() pro dynamické dotazy uvnitř funkce?

varování :tento styl s dynamickým SQL v SECURITY DEFINER funkce mohou být elegantní a pohodlné. Ale nepřehánějte to. Nevnořujte více úrovní funkcí tímto způsobem:

  • Styl je mnohem náchylnější k chybám než prostý SQL.
  • Přepínání kontextu pomocí SECURITY DEFINER má cenovku.
  • Dynamické SQL s funkcí EXECUTE nelze uložit a znovu použít plány dotazů.
  • Žádné „vkládání funkcí“.
  • A raději bych to vůbec nepoužíval pro velké dotazy na velkých stolech. Přidaná sofistikovanost může být překážkou výkonu. Jako:paralelismus je tímto způsobem zakázán pro plány dotazů.

To znamená, že vaše funkce vypadá dobře, nevidím žádný způsob pro SQL injection. format() je dobré zřetězit a citovat hodnoty a identifikátory pro dynamické SQL. Naopak, můžete odstranit určitou nadbytečnost, abyste to zlevnili.

Parametry funkce offset__i a limit__i jsou integer . Injekce SQL je nemožná přes celá čísla, opravdu není potřeba je citovat (i když SQL umožňuje řetězcové konstanty v uvozovkách pro LIMIT a OFFSET ). Takže jen:

format(' OFFSET %s LIMIT %s', offset__i, limit__i)

Také po ověření, že každý key__v je mezi vašimi legálními názvy sloupců – a přestože se jedná o legální názvy sloupců bez uvozovek – není nutné jej spouštět přes %I . Může být pouze %s

Raději bych použil text místo varchar . Není to velký problém, ale text je "preferovaný" typ řetězce.

Související:

COST 1 zdá se příliš nízké. Příručka:

Pokud nevíte lépe, ponechte COST na výchozí 100 .

Operace založená na jedné množině namísto všech smyček

Celá smyčka může být nahrazena jediným SELECT tvrzení. Měl by být znatelně rychlejší. Úkoly jsou v PL/pgSQL poměrně drahé. Takhle:

CREATE OR REPLACE FUNCTION goods__list_json (_options json, _limit int = NULL, _offset int = NULL, OUT _result jsonb)
    RETURNS jsonb
    LANGUAGE plpgsql SECURITY DEFINER AS
$func$
DECLARE
   _tbl  CONSTANT text   := 'public.goods_full';
   _cols CONSTANT text[] := '{id, id__category, category, name, barcode, price, stock, sale, purchase}';   
   _oper CONSTANT text[] := '{<, >, <=, >=, =, <>, LIKE, "NOT LIKE", ILIKE, "NOT ILIKE", BETWEEN, "NOT BETWEEN"}';
   _sql           text;
BEGIN
   SELECT concat('SELECT jsonb_agg(t) FROM ('
           , 'SELECT ' || string_agg(t.col, ', '  ORDER BY ord) FILTER (WHERE t.arr->>0 = 'true')
                                               -- ORDER BY to preserve order of objects in input
           , ' FROM '  || _tbl
           , ' WHERE ' || string_agg (
                             CASE WHEN (t.arr->>1)::int BETWEEN  1 AND 10 THEN
                                format('%s %s %L'       , t.col, _oper[(arr->>1)::int], t.arr->>2)
                                  WHEN (t.arr->>1)::int BETWEEN 11 AND 12 THEN
                                format('%s %s %L AND %L', t.col, _oper[(arr->>1)::int], t.arr->>2, t.arr->>3)
                               -- ELSE NULL  -- = default - or raise exception for illegal operator index?
                             END
                           , ' AND '  ORDER BY ord) -- ORDER BY only cosmetic
           , ' OFFSET ' || _offset  -- SQLi-safe, no quotes required
           , ' LIMIT '  || _limit   -- SQLi-safe, no quotes required
           , ') t'
          )
   FROM   json_each(_options) WITH ORDINALITY t(col, arr, ord)
   WHERE  t.col = ANY(_cols)        -- only allowed column names - or raise exception for illegal column?
   INTO   _sql;

   IF _sql IS NULL THEN
      RAISE EXCEPTION 'Invalid input resulted in empty SQL string! Input: %', _options;
   END IF;
   
   RAISE NOTICE 'SQL: %', _sql;
   EXECUTE _sql INTO _result;
END
$func$;

db<>fiddle zde

Kratší, rychlejší a stále bezpečný proti SQLi.

Uvozovky se přidávají pouze tam, kde je to nutné pro syntaxi nebo pro obranu proti SQL injection. Spálí se pouze pro filtrování hodnot. Názvy sloupců a operátory jsou ověřeny podle pevně nastaveného seznamu povolených možností.

Vstup je json místo jsonb . Pořadí objektů je zachováno v json , takže můžete určit pořadí sloupců v SELECT seznam (což má smysl) a WHERE podmínky (což je čistě kosmetické). Funkce nyní pozoruje obojí.

Výstup _result je stále jsonb . Pomocí OUT parametr místo proměnné. To je zcela volitelné, jen pro pohodlí. (Žádné explicitní RETURN vyžadováno prohlášení.)

Všimněte si strategického použití concat() k tichému ignorování NULL a operátoru zřetězení || takže NULL udělá zřetězený řetězec NULL. Tímto způsobem FROM , WHERE , LIMIT a OFFSET se vkládají pouze tam, kde je to potřeba. A SELECT příkaz funguje bez kteréhokoli z nich. Prázdný SELECT seznam (také legální, ale předpokládám, že nechtěný) vede k chybě syntaxe. Vše zamýšleno.
Pomocí format() pouze pro WHERE filtry, pro pohodlí a uvádět hodnoty. Viz:

Funkce není STRICT_limit a _offset mají výchozí hodnotu NULL , takže pouze první parametr _options je požadováno. _limit a _offset může být NULL nebo vynechán, pak je každý z příkazu odstraněn.

Pomocí text místo varchar .

Vytvořené konstantní proměnné ve skutečnosti CONSTANT (většinou pro dokumentaci).

Kromě toho tato funkce dělá to, co dělá váš originál.



  1. Získání jedinečných názvů sloupců omezení z databáze Oracle

  2. SELECT DISTINCT CLOB_COLUMN FROM TABLE;

  3. Tipy k tabulce Microsoft Access – triky a pokyny, část 5

  4. Oracle XMLTYPE extrakt založený na hodnotě a stavu