sql >> Database teknologi >  >> RDS >> PostgreSQL

Hvor sikkert er format() for dynamiske forespørgsler inde i en funktion?

Et ord til advarsel :denne stil med dynamisk SQL i SECURITY DEFINER funktioner kan være elegante og bekvemme. Men overbrug det ikke. Indlejr ikke flere niveauer af funktioner på denne måde:

  • Stilen er meget mere udsat for fejl end almindelig SQL.
  • Kontekstskiftet med SECURITY DEFINER har et prisskilt.
  • Dynamisk SQL med EXECUTE kan ikke gemme og genbruge forespørgselsplaner.
  • Ingen "funktion inlining".
  • Og jeg vil hellere slet ikke bruge det til store forespørgsler på store borde. Den ekstra sofistikering kan være en præstationsbarriere. Ligesom:parallelisme er deaktiveret for forespørgselsplaner på denne måde.

Når det er sagt, så ser din funktion godt ud, jeg ser ingen mulighed for SQL-injektion. format() har vist sig at være god til at sammenkæde og citere værdier og identifikatorer for dynamisk SQL. Tværtimod kan du måske fjerne noget redundans for at gøre det billigere.

Funktionsparametre offset__i og limit__i er integer . SQL-injektion er umulig gennem heltal, der er virkelig ingen grund til at citere dem (selvom SQL tillader citerede strengkonstanter for LIMIT og OFFSET ). Så bare:

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

Også efter at have verificeret, at hver key__v er blandt dine juridiske kolonnenavne - og selvom det alle er lovlige kolonnenavne uden anførselstegn - er det ikke nødvendigt at køre det gennem %I . Kan bare være %s

Jeg vil hellere bruge text i stedet for varchar . Ikke en big deal, men text er den "foretrukne" strengtype.

Relateret:

COST 1 synes for lavt. Manualen:

Medmindre du ved bedre, så lad COST stå ved standard 100 .

Enkelt sæt-baseret betjening i stedet for al looping

Hele loopingen kan erstattes med en enkelt SELECT udmelding. Skal være mærkbart hurtigere. Opgaver er forholdsvis dyre i PL/pgSQL. Sådan:

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 her

Kortere, hurtigere og stadig sikker mod SQLi.

Citater tilføjes kun, hvor det er nødvendigt for syntaks eller for at forsvare sig mod SQL-injektion. Brænder kun ned til filterværdier. Kolonnenavne og operatører verificeres i forhold til den fastkablede liste over tilladte muligheder.

Input er json i stedet for jsonb . Rækkefølgen af ​​objekter er bevaret i json , så du kan bestemme rækkefølgen af ​​kolonner i SELECT liste (som er meningsfuld) og WHERE forhold (som er rent kosmetisk). Funktionen observerer begge nu.

Output _result er stadig jsonb . Brug af en OUT parameter i stedet for variablen. Det er helt valgfrit, bare for nemheds skyld. (Ingen eksplicit RETURN erklæring påkrævet.)

Bemærk den strategiske brug af concat() for lydløst at ignorere NULL og sammenkædningsoperatoren || så NULL gør den sammenkædede streng til NULL. På denne måde FROM , WHERE , LIMIT , og OFFSET indsættes kun hvor det er nødvendigt. En SELECT statement fungerer uden nogen af ​​disse. En tom SELECT liste (også lovlig, men jeg formoder uønsket) resulterer i en syntaksfejl. Alt tilsigtet.
Ved brug af format() kun for WHERE filtre, for nemheds skyld og for at citere værdier. Se:

Funktionen er ikke STRICT længere. _limit og _offset har standardværdien NULL , så kun den første parameter _options er påkrævet. _limit og _offset kan være NULL eller udeladt, så fjernes hver enkelt fra sætningen.

Brug af text i stedet for varchar .

Lavede faktiske konstante variabler CONSTANT (mest til dokumentation).

Bortset fra det gør funktionen, hvad din original gør.



  1. Tilføj IIS 7 AppPool-identiteter som SQL Server-logoner

  2. 4 funktioner til at returnere måneden fra en dato i MariaDB

  3. Hvordan returnerer man flere rækker fra oracle-lagret procedure fra flere markører?

  4. Problem med Oracle Create View