PostgreSQL
 sql >> база данни >  >> RDS >> PostgreSQL

Колко сигурен е format() за динамични заявки във функция?

Едно предупреждение :този стил с динамичен SQL в SECURITY DEFINER функциите могат да бъдат елегантни и удобни. Но не прекалявайте с него. Не влагайте множество нива на функции по този начин:

  • Стилът е много по-податлив на грешки от обикновения SQL.
  • Превключването на контекста с SECURITY DEFINER има етикет с цена.
  • Динамичен SQL с EXECUTE не може да записва и използва повторно планове за заявки.
  • Без „вграждане на функции“.
  • И аз предпочитам изобщо да не го използвам за големи заявки на големи маси. Добавената изтънченост може да бъде бариера за производителността. Като:паралелизмът е деактивиран за планове на заявки по този начин.

Въпреки това функцията ви изглежда добре, не виждам начин за SQL инжекция. format() е доказано добър за свързване и цитиране на стойности и идентификатори за динамичен SQL. Напротив, можете да премахнете някои излишъци, за да стане по-евтино.

Функционални параметри offset__i и limit__i са integer . SQL инжектирането е невъзможно чрез цели числа, наистина няма нужда да ги поставяте в кавички (въпреки че SQL позволява константи на низове в кавички за LIMIT и OFFSET ). Така че просто:

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

Освен това, след като проверите, че всеки key__v е сред законните ви имена на колони - и въпреки че всички те са законни имена на колони без кавички - няма нужда да го пускате през %I . Може да бъде само %s

Предпочитам да използвам text вместо varchar . Не е голяма работа, но text е "предпочитаният" тип низ.

Свързани:

COST 1 изглежда твърде ниско. Ръководството:

Освен ако не знаете по-добре, оставете COST по подразбиране 100 .

Единствена операция, базирана на набор, вместо целия цикъл

Целият цикъл може да бъде заменен с един SELECT изявление. Трябва да е значително по-бързо. Присвояванията са сравнително скъпи в PL/pgSQL. Като това:

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 тук

По-кратък, по-бърз и все още безопасен срещу SQLi.

Кавичките се добавят само когато е необходимо за синтаксис или за защита срещу SQL инжектиране. Изгаря само до филтрирани стойности. Имената на колоните и операторите се проверяват спрямо фиксирания списък с разрешени опции.

Входът е json вместо jsonb . Редът на обектите се запазва в json , така че можете да определите последователността на колоните в SELECT списък (което е смислено) и WHERE условия (което е чисто козметично). Функцията наблюдава и двете сега.

Изход _result все още е jsonb . Използване на OUT параметър вместо променливата. Това е напълно незадължително, само за удобство. (Без изрично RETURN изисква се изявление.)

Обърнете внимание на стратегическото използване на concat() за тихо игнориране на NULL и оператора за конкатенация || така че NULL прави свързания низ NULL. По този начин, FROM , WHERE , LIMIT и OFFSET се поставят само където е необходимо. A SELECT изявлението работи без нито едно от тях. Празен SELECT списък (също легален, но предполагам нежелан) води до синтактична грешка. Всичко по предназначение.
Използване на format() само за WHERE филтри, за удобство и за котиране на стойности. Вижте:

Функцията не е STRICT вече. _limit и _offset имат стойност по подразбиране NULL , така че само първият параметър _options изисква се. _limit и _offset може да бъде NULL или пропуснато, след което всеки се премахва от оператора.

Използване на text вместо varchar .

Направи постоянни променливи всъщност CONSTANT (най-вече за документация).

Освен това функцията прави това, което вашият оригинал прави.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да задам функция на PostgreSQL като стойност по подразбиране в GORM?

  2. JDBC драйверът не е наличен за „org.postgresql.Driver“ за Spring Roo

  3. Как да получите разлика от дни/месеци/години (datediff) между две дати?

  4. Как да стигнем до края на деня?

  5. PgBouncer 1.7 – „Цветовете варират след възкресението“