sql - 从用户输入中使用动态列名称打开游标的安全方法

标签 sql sql-injection plpgsql dynamic-sql postgresql-9.4

我正在尝试编写打开带有动态列名称的游标的函数。 我担心这里明显的 SQL 注入(inject)可能性。 我很高兴在fine manual中看到这可以很容易地完成,但是当我在我的示例中尝试时,它出错了

error: column does not exist.

我当前的尝试可以浓缩为 this SQL Fiddle 。下面,我展示了这个 fiddle 的格式化代码。

tst() 函数的目标是能够计算常量查询的任何给定列中值的不同出现次数。

我要求提示我做错了什么,或者也许有一些替代方法可以安全地实现相同的目标。

    CREATE TABLE t1 (
        f1 character varying not null,
        f2 character varying not null
    );
    CREATE TABLE t2 (
        f1 character varying not null,
        f2 character varying not null
    );
    INSERT INTO t1 (f1,f2) VALUES ('a1','b1'), ('a2','b2');
    INSERT INTO t2 (f1,f2) VALUES ('a1','c1'), ('a2','c2');

    CREATE OR REPLACE FUNCTION tst(p_field character varying)
        RETURNS INTEGER AS
    $BODY$ 
    DECLARE 
        v_r record; 
        v_cur refcursor; 
        v_sql character varying := 'SELECT count(DISTINCT(%I)) as qty 
                                    FROM t1 LEFT JOIN t2 ON (t1.f1=t2.f1)'; 
    BEGIN  
        OPEN v_cur FOR EXECUTE format(v_sql,lower(p_field)); 
        FETCH v_cur INTO v_r; 
        CLOSE v_cur; 
        return v_r.qty; 
    END; 
    $BODY$ 
    LANGUAGE plpgsql;

测试执行:

SELECT tst('t1.f1')

提供错误消息:

ERROR: column "t1.f1" does not exist
Hint: PL/pgSQL function tst(character varying) line 1 at OPEN

最佳答案

这会起作用:

SELECT tst('f1');

您面临的问题:format()将与 %I 连接的参数解释为 one 标识符。您正在尝试传递由两个标识符组成的表限定列名,该标识符被解释为“t1.f1”(一个名称,用双引号引起来保留否则名称中的点是非法的。

如果您想传递表列名称,请使用两个参数:

CREATE OR REPLACE FUNCTION tst2(_col text, _tbl text = NULL)
  RETURNS int AS
$func$
DECLARE
   v_r record;
   v_cur refcursor;
   v_sql text := 'SELECT count(DISTINCT %s) AS qty
                  FROM t1 LEFT JOIN t2 USING (f1)';
BEGIN
   OPEN v_cur FOR EXECUTE
      format(v_sql, CASE WHEN _tbl <> ''  -- rule out NULL and ''
                         THEN quote_ident(lower(_tbl)) || '.' ||
                              quote_ident(lower(_col))
                         ELSE quote_ident(lower(_col)) END);
   FETCH v_cur INTO v_r;
   CLOSE v_cur;
   RETURN v_r.qty;
END
$func$ LANGUAGE plpgsql;

旁白:它是DISTINCT f1 - 列名周围没有括号,除非您想将其设为行类型。

实际上,您根本不需要光标。更快、更简单:

CREATE OR REPLACE FUNCTION tst3(_col text, _tbl text = NULL, OUT ct bigint) AS
$func$
BEGIN
   EXECUTE format('SELECT count(DISTINCT %s) AS qty
                   FROM t1 LEFT JOIN t2 USING (f1)'
                 , CASE WHEN _tbl <> ''  -- rule out NULL and ''
                        THEN quote_ident(lower(_tbl)) || '.' ||
                             quote_ident(lower(_col))
                        ELSE quote_ident(lower(_col)) END)
   INTO ct;
   RETURN;
END
$func$ LANGUAGE plpgsql;

为了方便起见,我提供了 NULL 作为参数默认值。这样,您可以仅使用列名称或使用列和表名称来调用该函数。但不能没有列名称。

调用:

SELECT tst3('f1', 't1');
SELECT tst3('f1');
SELECT tst3(_col := 'f1');

test2()相同。

SQL Fiddle.

相关答案:

关于sql - 从用户输入中使用动态列名称打开游标的安全方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30192248/

相关文章:

sql - 我应该执行什么 SQL 查询才能获得预期的结果集?

MySQL:计算空行的累积和

php - 为什么不反斜杠每个空格以防止 mysql 注入(inject)

c# - 如何在 LINQ 中按子集合值进行选择

Sql 从另一个表的一组结果中以数组形式查询新列

php - 防止对具有可变(和大)列数的查询进行 sql 注入(inject)

java - 错误代码 : Why is this dangerous?

postgresql - 如何使用 for 循环选择字段值并将它们插入到另一个表中

sql - 处理 PostgreSQL 异常的优雅方式?

postgresql - 要忽略 PostgreSQL 的 BEFORE TRIGGER 中的结果?