26ai 的 SQL Firewall 刚出来的时候,安全部门眼睛都亮了:"终于能在数据库内核里做零信任了!"我负责试点上线,结果第一周就把财务部的月末报表给拦了。报表系统跑的是 Cognos 生成的动态 SQL,每次查询的 WHERE 条件组合都不一样,SQL Firewall 的学习期根本抓不全。
3.1 指纹生成不是字符串匹配
很多人以为 SQL Firewall 就是存个 SQL 文本做白名单,实际上它存的是"规范化解析树签名"。具体做法是:把字面量替换成绑定变量占位符,去掉多余空格和注释,然后对解析树里的对象 ID、列 ID、操作符类型做哈希。这意味着 SELECT * FROM emp WHERE empno=1 和 SELECT * FROM emp WHERE empno=2 是同一个指纹,但 SELECT * FROM emp WHERE empno=1 和 SELECT * FROM emp WHERE ename='A' 就是两个指纹。
这个设计很妙,但也带来一个问题:对于 BI 工具生成的即席查询(Ad-hoc SQL),由于列组合千变万化,学习期永远学不完。我们的 Cognos 报表有 300 多张表关联,学习期跑了一周,allow list 里攒了 12 万条指纹,内存占用 800MB,查询匹配延迟从 2ms 涨到 15ms。
3.2 实验:从学习到阻断的完整周期
-- 创建防火墙规则
BEGIN
DBMS_SQL_FIREWALL.CREATE_ALLOW_LIST(
allow_list_name => 'APP_ALLOW_LIST',
top_level_only => FALSE,
allowed_client_programs => 'java.exe,sqlplus.exe'
);
DBMS_SQL_FIREWALL.START_CAPTURE(
allow_list_name => 'APP_ALLOW_LIST',
capture_schemas => 'APP_USER',
capture_mode => DBMS_SQL_FIREWALL.CAPTURE_ALL
);
END;
/
-- 模拟攻击:尝试注入
SELECT emp_name, salary FROM employees WHERE dept_id = 10 OR 1=1;
-- ORA-47625: SQL Firewall violation
-- 查看违规记录
SELECT sql_text, violation_type, client_program
FROM V$SQL_FIREWALL_VIOLATIONS
WHERE capture_time > SYSDATE - 1;
我们最终的折中方案是:对 OLTP 应用账号(如 APP_USER)开严格模式 BLOCK,对报表只读账号(如 RO_USER)开审计模式 AUDIT_ONLY,同时把 Cognos 的客户端程序名加入豁免列表。另外,学习期不要设太短,我们后来改成 30 天,并且跨一个完整的月末结账周期,这样才抓得全。
还有个细节:SQL Firewall 的指纹存在共享池里,如果实例重启,需要重新从磁盘表加载。我们在一次维护重启后发现防火墙"失效"了 3 分钟,后来查明白是加载 12 万条指纹到共享池时,Library Cache 争用严重。解决办法是重启前先把 allow list 导出到 trace,重启后分批导入。