如何安全调用表中存储的函数?替换动态SQL规避SQL注入
场景说明
表condition:存储函数名的表
create table condition(id int,name varchar(50),fn_name varchar(50));
测试数据
insert into condition values(1,'检查ID','fn_condition_1'); insert into condition values(2,'检查名称','fn_condition_2'); insert into condition values(3,'检查薪资','fn_condition_3');
示例函数fn_condition_1
create or replace function fn_condition_1(p_id int) returns boolean as $$ begin if(select count(1) from emp where id = p_id) > 0 then return true; else return false; end if; end $$ language plpgsql;
原存储过程prc_bsn_condition(存在SQL注入风险)
create or replace procedure prc_bsn_condition(p_id int) language plpgsql as $$ declare v_out varchar(10); v_sql varchar(200); v_fn varchar(20); begin select fn_name into v_fn from condition where id = p_id; v_sql := 'SELECT * FROM '||v_fn||'('||p_id||')'; execute v_sql into v_out; raise info '%',v_out; end $$;
原存储过程输出
true;
需求
上述存储过程运行正常,但存在SQL注入风险,希望将动态SQL转换为静态SQL,或找到其他安全调用表中存储函数的方法。
安全解决方案
方法1:参数化动态SQL(兼顾灵活性与安全性)
PostgreSQL的EXECUTE支持参数绑定,配合quote_ident()转义函数名,可彻底规避注入风险:
create or replace procedure prc_bsn_condition_safe(p_id int) language plpgsql as $$ declare v_out boolean; v_fn varchar(20); begin select fn_name into v_fn from condition where id = p_id; -- 用quote_ident转义函数名,参数通过using绑定而非拼接 execute format('SELECT %s($1)', quote_ident(v_fn)) into v_out using p_id; raise info '%',v_out; end $$;
quote_ident()会自动转义函数名中的特殊字符或关键字,防止恶意构造的函数名触发注入using p_id将参数绑定到动态SQL中,避免直接拼接参数值带来的风险
方法2:静态分支调用(零注入风险)
如果函数数量固定且较少,可直接用CASE分支静态调用函数,完全消除动态SQL:
create or replace procedure prc_bsn_condition_static(p_id int) language plpgsql as $$ declare v_out boolean; v_fn varchar(20); begin select fn_name into v_fn from condition where id = p_id; case v_fn when 'fn_condition_1' then v_out := fn_condition_1(p_id); when 'fn_condition_2' then v_out := fn_condition_2(p_id); when 'fn_condition_3' then v_out := fn_condition_3(p_id); else raise exception '未知函数: %', v_fn; end case; raise info '%',v_out; end $$;
- 全程使用静态SQL,无注入风险
- 缺点是新增函数时需修改存储过程,适合函数数量固定的场景
方法3:先验证函数合法性再调用
通过查询系统表pg_proc验证函数的存在性、参数类型和返回类型,进一步加固安全:
create or replace procedure prc_bsn_condition_validate(p_id int) language plpgsql as $$ declare v_out boolean; v_fn varchar(20); begin select fn_name into v_fn from condition where id = p_id; -- 验证函数是否存在,且参数、返回类型匹配 if not exists ( select 1 from pg_proc where proname = v_fn and proargtypes[0] = 'int'::regtype and prorettype = 'boolean'::regtype ) then raise exception '函数不存在或类型不匹配: %', v_fn; end if; -- 安全执行动态SQL execute format('SELECT %s($1)', quote_ident(v_fn)) into v_out using p_id; raise info '%',v_out; end $$;
- 先校验函数合法性,避免调用恶意构造的函数名
- 结合参数化动态SQL,在灵活性和安全性之间取得平衡
内容的提问来源于stack exchange,提问作者MAK
相关产品推荐
相关产品推荐

