Oracle动态列查询存储过程是否存在SQL注入风险?求安全实现方案
你的存储过程存在严重的SQL注入风险!
兄弟,你这个写法确实简洁,但安全隐患可不小——当前实现完全不安全,因为columns参数是直接拼接进SQL语句的,攻击者可以通过构造恶意输入执行任意SQL命令。
举个直观的例子,如果有人调用这个存储过程时传入:
* FROM MySchema.Table1; DROP TABLE MySchema.Table2; --
最终执行的SQL会变成:
SELECT * FROM MySchema.Table1; DROP TABLE MySchema.Table2; -- FROM MySchema.Table1
这会直接删除你的Table2,后果不堪设想。
为什么参数化查询在这里没用?因为SQL的参数化只能处理值,不能处理表名、列名这类标识符,所以你没法把columns作为参数传入,只能通过拼接,但必须先做严格的合法性验证。
正确的实现方式:列名校验+白名单机制
在Oracle中,你可以通过查询数据字典表(比如USER_TAB_COLUMNS)来验证传入的列名是否确实存在于MySchema.Table1中,只有合法的列名才允许拼接。具体步骤如下:
- 将传入的
columns字符串按逗号拆分成单个列名 - 逐个检查每个列名是否在
Table1的列列表中 - 如果存在非法列名,直接抛出错误;如果全部合法,再拼接成安全的SQL语句
下面是改进后的存储过程示例:
CREATE OR REPLACE PROCEDURE MySchema.Search(columns IN VARCHAR2, res_out OUT SYS_REFCURSOR) IS v_valid_columns VARCHAR2(4000) := ''; v_col VARCHAR2(128); v_start_pos PLS_INTEGER := 1; v_end_pos PLS_INTEGER; BEGIN -- 处理空列名的异常情况 IF columns IS NULL OR TRIM(columns) = '' THEN RAISE_APPLICATION_ERROR(-20001, '列名参数不能为空'); END IF; -- 拆分传入的列名字符串(按逗号分隔) LOOP v_end_pos := INSTR(columns, ',', v_start_pos); IF v_end_pos = 0 THEN v_col := TRIM(SUBSTR(columns, v_start_pos)); v_start_pos := LENGTH(columns) + 1; ELSE v_col := TRIM(SUBSTR(columns, v_start_pos, v_end_pos - v_start_pos)); v_start_pos := v_end_pos + 1; END IF; -- 验证列名是否存在于Table1中(Oracle字典表默认存大写,所以统一转大写校验) BEGIN SELECT column_name INTO v_col FROM USER_TAB_COLUMNS WHERE table_name = 'TABLE1' AND column_name = UPPER(v_col); -- 合法列名加入有效列表 IF v_valid_columns IS NOT NULL THEN v_valid_columns := v_valid_columns || ', '; END IF; v_valid_columns := v_valid_columns || v_col; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20002, '非法列名: ' || v_col); END; EXIT WHEN v_start_pos > LENGTH(columns); END LOOP; -- 拼接并执行安全的动态SQL OPEN res_out FOR 'SELECT ' || v_valid_columns || ' FROM MySchema.Table1'; END Search; /
这个实现的安全优势:
- 所有传入的列名都必须在
Table1的合法列列表中,否则直接阻断执行 - 自动处理列名的大小写问题(适配Oracle字典表的存储规则)
- 从根源上避免了恶意SQL注入的可能
如果你的业务场景中允许查询的列是固定的,也可以直接用硬编码白名单的方式(比如提前定义一个列名数组,检查传入列是否在数组内),这种方式性能会更优。
内容的提问来源于stack exchange,提问作者Lucy82
相关产品推荐
相关产品推荐

