sql >> Database >  >> RDS >> PostgreSQL

Hoe veilig is format() voor dynamische queries binnen een functie?

Een waarschuwing :deze stijl met dynamische SQL in SECURITY DEFINER functies kunnen elegant en handig zijn. Maar gebruik het niet te veel. Nest niet meerdere niveaus van functies op deze manier:

  • De stijl is veel foutgevoeliger dan gewone SQL.
  • De contextwisseling met SECURITY DEFINER heeft een prijskaartje.
  • Dynamische SQL met EXECUTE kan queryplannen niet opslaan en opnieuw gebruiken.
  • Geen "functie inlining".
  • En ik gebruik het liever helemaal niet voor grote query's op grote tabellen. De toegevoegde verfijning kan een prestatiebelemmering zijn. Zoals:parallellisme is op deze manier uitgeschakeld voor queryplannen.

Dat gezegd hebbende, je functie ziet er goed uit, ik zie geen manier voor SQL-injectie. format() is bewezen goed in het samenvoegen en citeren van waarden en identifiers voor dynamische SQL. Integendeel, je zou wat redundantie kunnen verwijderen om het goedkoper te maken.

Functieparameters offset__i en limit__i zijn integer . SQL-injectie is onmogelijk via gehele getallen, het is echt niet nodig om ze te citeren (ook al staat SQL stringconstanten tussen aanhalingstekens toe voor LIMIT en OFFSET ). Dus gewoon:

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

Ook, na te hebben geverifieerd dat elke key__v is een van uw legale kolomnamen - en hoewel dit allemaal legale, niet-geciteerde kolomnamen zijn - is het niet nodig om het door %I te halen . Kan gewoon %s . zijn

Ik gebruik liever text in plaats van varchar . Geen big deal, maar text is het "voorkeur" tekenreekstype.

Gerelateerd:

COST 1 lijkt te laag. De handleiding:

Tenzij je beter weet, laat COST op zijn standaard 100 .

Op één set gebaseerde bewerking in plaats van alle lussen

De hele looping kan worden vervangen door een enkele SELECT uitspraak. Zou merkbaar sneller moeten zijn. Opdrachten zijn relatief duur in PL/pgSQL. Zoals dit:

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 hier

Korter, sneller en toch veilig tegen SQLi.

Aanhalingstekens worden alleen toegevoegd waar nodig voor syntaxis of ter verdediging tegen SQL-injectie. Brandt alleen naar filterwaarden. Kolomnamen en operators worden geverifieerd aan de hand van de vaste lijst met toegestane opties.

Invoer is json in plaats van jsonb . Volgorde van objecten blijft behouden in json , zodat u de volgorde van kolommen kunt bepalen in de SELECT lijst (wat zinvol is) en WHERE omstandigheden (wat puur cosmetisch is). De functie observeert nu beide.

Uitvoer _result is nog steeds jsonb . Een OUT gebruiken parameter in plaats van de variabele. Dat is volledig optioneel, alleen voor het gemak. (Geen expliciete RETURN verklaring vereist.)

Let op het strategische gebruik van concat() om NULL en de aaneenschakelingsoperator || in stilte te negeren zodat NULL de aaneengeschakelde tekenreeks NULL maakt. Op deze manier, FROM , WHERE , LIMIT , en OFFSET worden alleen geplaatst waar nodig. Een SELECT verklaring werkt zonder een van beide. Een lege SELECT list (ook legaal, maar ik veronderstel ongewenst) resulteert in een syntaxisfout. Allemaal bedoeld.
Met behulp van format() alleen voor WHERE filters, voor het gemak en om waarden te citeren. Zie:

De functie is niet STRICT niet meer. _limit en _offset hebben standaardwaarde NULL , dus alleen de eerste parameter _options Is benodigd. _limit en _offset kan NULL zijn of worden weggelaten, dan wordt elk van de instructie verwijderd.

text gebruiken in plaats van varchar .

Constante variabelen gemaakt eigenlijk CONSTANT (meestal voor documentatie).

Verder doet de functie wat je origineel doet.



  1. SQL-volgorde per telling

  2. tabellen en kolommen dynamisch maken met behulp van mysql python connector

  3. SQL Geneste Query traag met IN

  4. Hoe kan ik gegevens in de MySQL-database invoegen?