游客发表
很多人以为上了参数化查询就万事大吉,种s注入结果还是解决被注了。问题不在参数化本身,容易在于用错了地方。产生
我见过一个真实案例。漏洞某电商后台的种s注入订单搜索接口,开发用了 MyBatis 的解决 #{ } 占位符,看起来没问题。容易但排序字段是产生前端传的,代码里写的漏洞是 ORDER BY ${ sortColumn}。用 ${ } 做字符串拼接,种s注入等于把用户输入直接塞进 SQL 语句。解决攻击者把 sortColumn 传成 id ASC; DROP TABLE orders;--,容易数据库直接执行。产生参数化只保护了值的漏洞位置,没保护标识符的位置——表名、列名、排序方向,这些地方用参数化根本没用,因为预编译阶段数据库就要确定这些结构。
另一种常见做法是手写转义函数。比如 PHP 里用 addslashes(),或者自己写个 str_replace("'", "''", $input)。这种方案漏洞最多。原因很简单:不同数据库的转义规则不一样。MySQL 在 GBK 编码下,%bf%27 这样的字节序列会被解析成一个合法多字节字符,反斜杠转义直接失效。攻击者构造 %bf%27 OR 1=1--,你的转义函数把单引号变成 \',但前面的 %bf 和反斜杠组合成了另一个字符,单引号逃逸出来。这就是经典的宽字节注入。转义函数还容易漏掉数字型注入点——开发者觉得数字不用转义,直接拼进 SQL,结果 1 OR 1=1 畅通无阻。
Hibernate、MyBatis 这些 ORM 也不是保险箱。Hibernate 的 HQL 拼接、MyBatis 的 ${ }、JPA 的 createNativeQuery 里手动拼字符串,都是重灾区。我审计过一个项目,用的是 Spring Data JPA,看起来全是方法名查询,很规范。但有个复杂报表查询,开发者嫌 Specification 写起来麻烦,直接注入了 EntityManager,用字符串拼了个原生 SQL。那个拼接点藏在一个工具类里,参数从 HTTP 请求一路透传,中间没有任何校验。这种漏洞最危险,因为代码审查时大家看到 JPA 就放松了警惕。
预编译语句(PreparedStatement)确实是目前最可靠的方案,但它有明确的适用边界。值的位置它能保护,结构的位置它无能为力。动态表名、动态列名、动态排序、动态分组,这些场景下预编译帮不上忙。正确的做法是白名单校验——排序字段只允许 id、create_time、price 这几个值,传别的直接拒绝。别试图用正则过滤,正则写不好照样被绕过。白名单是枚举,枚举没有绕过空间。
有些团队把所有 SQL 塞进存储过程,觉得这样应用层就安全了。但存储过程内部如果用了 EXEC 或 EXECUTE IMMEDIATE 动态拼接,注入点只是从应用层转移到了数据库层。而且存储过程的参数类型如果声明为 VARCHAR 然后在内部拼接,和直接在应用层拼接没有本质区别。更麻烦的是,存储过程的代码审计工具支持差,很多静态扫描工具根本扫不到里面的问题。
说到底,容易出漏洞的不是某一种方案本身,而是“用了一半”的方案。参数化查询配动态拼接、转义函数配错误编码、ORM 配原生 SQL 混写——这些半吊子做法比彻底不用防护更危险,因为它们给了开发者虚假的安全感。真正靠谱的做法只有一条:值用参数化,结构用白名单,两者都不留拼接的口子。做不到这一点,用什么方案都是漏。
友情链接