You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何安全调用表中存储的函数?替换动态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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:46:10