防止SQL注入的核心思路不是只靠应用层的参数化查询,而是在数据库代理层通过SQL解析器对每一条SQL语句做语法级别的规则校验。具体做法是:在数据库代理中间件中部署SQL解析引擎,配置一套严格的白名单规则和黑名单规则,对所有进入数据库的SQL进行词法分析、语法树构建和语义校验,把不符合规则的语句直接拦截在代理层,根本不让它到达后端数据库。这套方案的优势在于对业务代码零侵入,不管你用什么语言、什么框架,只要流量经过代理就能被保护。
什么是数据库代理SQL解析器
数据库代理SQL解析器是部署在应用和数据库之间的一个中间层组件。它的工作原理是先把客户端发来的SQL语句截获下来,然后用专门的SQL解析器(比如基于ANTLR、Druid SQL Parser或者自研的词法分析器)把SQL拆分成Token流,再构建成抽象语法树(AST)。有了语法树之后,就可以对SQL的结构、关键字、表名、字段名、操作类型等做精细化的规则匹配。只有通过所有规则校验的SQL才会被放行转发到真实数据库,否则直接返回错误或者丢弃连接。
这和传统WAF的区别在于:WAF是基于正则表达式做字符串匹配,容易被绕过;而SQL解析器是真正理解SQL语法结构的,能识别出经过编码、拼接、注释混淆等手段伪装的注入语句,防护精度高出一个量级。
规则配置的核心框架
一套完整的SQL解析器规则配置通常包含以下几个维度:操作类型白名单、关键字黑名单、表名和字段名白名单、参数绑定检测、危险函数拦截、注释和特殊字符过滤。下面逐个展开讲。
第一类:操作类型白名单规则
这是最基础也是最重要的规则。你需要根据业务场景明确规定哪些类型的SQL操作是被允许的。比如一个只做查询的报表系统,就只允许SELECT;一个内容管理后台,可能允许SELECT、INSERT、UPDATE,但绝对不允许DELETE和DROP。配置方式通常是在规则引擎中定义允许的操作类型列表:
rules:
- name: "allow_select_only"
operation_type: ["SELECT"]
action: "pass"
- name: "allow_crud"
operation_type: ["SELECT", "INSERT", "UPDATE"]
action: "pass"
- name: "deny_ddl"
operation_type: ["DROP", "ALTER", "TRUNCATE", "CREATE"]
action: "block"
配置好之后,解析器在构建语法树时首先提取根节点的操作类型,和白名单做比对,不在白名单内的直接拦截。这一步能挡住绝大多数高危操作。
第二类:关键字黑名单规则
有些关键字本身就带有攻击性,比如UNION、INTO OUTFILE、LOAD_FILE、BENCHMARK、SLEEP、EXECUTE等。这些关键字出现在非预期的上下文中就是明显的注入信号。规则配置需要把这些关键字列入黑名单,并且设定上下文条件。比如UNION在子查询中可能是合法的,但在主查询中配合没有LIMIT的情况就很可疑。
blacklist_keywords:
- keyword: "UNION"
context: "root_select"
action: "block"
- keyword: "INTO OUTFILE"
action: "block"
- keyword: "LOAD_FILE"
action: "block"
- keyword: "BENCHMARK"
action: "block"
- keyword: "SLEEP"
action: "block"
- keyword: "EXECUTE"
context: "dynamic_sql"
action: "block"
注意这里的context字段,它表示只有在特定语法位置出现才拦截,避免误杀正常业务SQL。这种细粒度控制是SQL解析器相比正则匹配的核心优势。
第三类:表名和字段名白名单规则
SQL注入经常通过篡改表名和字段名来探测数据库结构,比如用information_schema.tables来枚举表信息。所以必须配置严格的表名和字段名白名单。你需要把业务系统中实际用到的所有表和字段提前录入规则库,解析器在校验时会检查SQL中引用的每一个标识符是否在白名单中。
whitelist:
tables:
- "users"
- "orders"
- "products"
- "order_items"
columns:
- table: "users"
columns: ["id", "username", "email", "created_at"]
- table: "orders"
columns: ["id", "user_id", "total", "status", "created_at"]
如果解析器发现SQL中引用了不在白名单里的表或字段,比如出现了"information_schema"或者某个不存在的表名,直接拦截。这个规则对防止盲注和信息泄露特别有效。
第四类:参数绑定检测规则
合法的参数化查询会使用占位符(?或者:name或者$1等),而SQL注入往往是把参数直接拼接到SQL字符串里。解析器可以通过检测SQL中是否存在未绑定的字面量字符串拼接来判断风险。具体做法是在语法树中查找字符串常量直接出现在WHERE条件、LIMIT、ORDER BY等位置的情况,如果发现就标记为可疑。
parameter_check:
- pattern: "string_literal in where_clause"
severity: "high"
action: "block"
- pattern: "string_literal in limit_clause"
severity: "high"
action: "block"
- pattern: "concatenation in condition"
severity: "medium"
action: "alert"
当然,有些业务确实需要在WHERE中用字符串常量做过滤,所以这个规则要配合白名单表和字段一起用,避免误报。建议初期设为告警模式,观察一段时间后再收紧为拦截。
第五类:危险函数拦截规则
MySQL、PostgreSQL、SQL Server等数据库都有大量内置函数,其中一些可以被利用来执行系统命令或者读取文件。常见的危险函数包括:LOAD_FILE()、INTO OUTFILE()、DUMPFILE()、SYS_EXEC()、XP_CMDSHELL()、UTL_HTTP()等。规则配置需要针对不同数据库类型分别设置。
dangerous_functions:
mysql:
- "LOAD_FILE"
- "INTO OUTFILE"
- "DUMPFILE"
- "BENCHMARK"
- "SLEEP"
- "EXTRACTVALUE"
- "UPDATEXML"
postgresql:
- "COPY FROM PROGRAM"
- "LO_IMPORT"
sqlserver:
- "xp_cmdshell"
- "sp_oacreate"
解析器在语法树中遍历所有函数调用节点,一旦匹配到黑名单中的函数名就拦截。这里要注意函数名大小写的问题,解析器需要做统一的大小写归一化处理。
第六类:注释和特殊字符过滤规则
攻击者经常用注释符(--、#、/* */)来截断SQL语句或者隐藏注入载荷。解析器规则需要检测SQL中注释的使用方式是否合规。正常业务SQL很少使用行内注释,如果出现大量注释或者注释出现在关键位置就要警惕。另外像分号(;)用来拼接多条语句、双横线(--)用来注释掉后续内容,这些都需要重点监控。
comment_rules:
- pattern: "inline_comment with --"
position: ["after_where", "after_limit"]
action: "block"
- pattern: "block_comment /* */"
action: "alert"
- pattern: "multiple_statements with ;"
action: "block"
规则配置的最佳实践
在实际部署中,规则配置不是一次性写完就完事的,需要遵循几个原则。第一,先松后紧。刚上线时把大部分规则设为告警模式,收集一周左右的日志,分析误报情况,逐步调整为拦截模式。第二,分业务场景配置。不同的业务系统SQL特征不一样,报表系统和交易系统的规则应该分开配置,不要用一套规则打天下。第三,定期更新规则库。新的注入手法和新的数据库函数不断出现,规则库需要持续维护。
第四,做好性能平衡。SQL解析器对每条SQL都要做语法树构建和规则匹配,如果规则太复杂会影响查询延迟。建议把高频的简单规则放在前面快速判断,复杂的语义分析放在后面,同时对解析结果做缓存,相同结构的SQL不需要重复解析。
第五,配合审计日志。所有被拦截的SQL都要记录完整的原始语句、来源IP、时间戳、命中的规则编号,方便后续安全分析和溯源。日志格式建议统一用JSON,方便接入安全分析平台。
常见数据库代理产品的规则配置差异
目前市面上主流的数据库代理产品在SQL解析器规则配置上各有特点。开源方案比如ShardingSphere-Proxy、MyCat、ProxySQL都支持一定程度的SQL拦截,但原生规则比较简单,需要二次开发才能实现精细化的AST级别校验。商业产品比如一些云厂商的数据库代理服务、专业的数据库防火墙产品,通常内置了更完善的规则引擎和威胁情报库,开箱即用但灵活性稍差。自研方案的好处是可以完全贴合业务,但开发和维护成本高,需要有专门的安全团队持续跟进。
不管用哪种方案,核心逻辑都是一样的:解析SQL结构、匹配规则、决定放行或拦截。区别只在于解析器的精度、规则引擎的表达能力和运维的便捷程度。
总结与建议
防止SQL注入不能只依赖应用层的代码规范,数据库代理SQL解析器提供了一个独立于业务代码的防护层,通过语法级别的规则配置实现精准拦截。关键是要把操作类型白名单、关键字黑名单、表名字段白名单、参数绑定检测、危险函数拦截、注释过滤这六类规则组合使用,并且根据业务场景做差异化配置。上线后持续监控误报率和漏报率,不断迭代优化规则库,才能真正建立起可靠的SQL注入防线。这套方案特别适合多业务系统共享数据库、老旧系统无法改造代码、以及对安全合规有严格要求的场景。
