快速识别SQL注入风险的方法是检查是否直接拼接用户输入到SQL字符串中,而非使用参数化查询;重点关注字符串拼接、f-string、格式化函数及ORM原生SQL接口中的动态标识符拼接。

怎么快速识别哪些SQL查询接口存在注入风险
直接看是否拼接用户输入进 query 字符串,而不是走参数化查询。只要看到 + req.query.id、f"SELECT * FROM users WHERE id = {user_id}" 或 String.format("WHERE name = '%s'", name) 这类写法,基本就中招了。
实操建议:
- 用正则扫代码库:
grep -r "SELECT\|INSERT\|UPDATE\|DELETE" src/ | grep -E "\$\{.*\}|\+.*\+|f\".*\{.*\}|'.*%s.*'" - 重点关注 ORM 的原生 SQL 接口,比如
sequelize.query()、sqlalchemy.text()、MyBatis ${}(不是#{}) - 别信“前端做了校验”,后端没做参数化,校验形同虚设
为什么预编译语句能防注入,但有些写法还是漏掉
预编译防注入的本质是:把 SQL 结构和数据分离,让数据库引擎不把参数值当语法解析。但很多人误以为用了 PreparedStatement 就万事大吉,其实只要参数进了 SQL 拼接位置,就失效了。
常见翻车点:
-
String sql = "SELECT * FROM " + tableName + " WHERE id = ?";—— 表名不能参数化,?只能代入值,不能代入标识符 -
ORDER BY ?或GROUP BY ?—— 大多数驱动不支持对排序字段做参数化,实际执行时仍会拼接 - 动态
IN列表:写成WHERE id IN (?)只能匹配单个值,多个值必须生成对应数量的?占位符,硬编码或用工具生成
如何量化一个服务的SQL注入攻击面大小
攻击面不是“有没有漏洞”,而是“攻击者能触达多少可拼接的SQL入口点”。数清楚以下三类加起来就是基数:
- HTTP 接口里显式接收字符串参数并用于 SQL 拼接的路径数(如
/api/user?name=xxx且 name 直接进WHERE name = 'xxx') - 内部 RPC 或消息消费逻辑中,从外部来源(MQ payload、第三方 webhook body)提取字符串构造 SQL 的调用点
- 管理后台或调试接口中未鉴权的原始 SQL 执行框(比如
/debug/sql),哪怕只对内网开放,也算攻击面一部分
注意:同一个接口若同时接受 id、sort、filter 三个可拼接字段,算作 3 个独立攻击向量,不是 1 个。
扫描工具报“疑似注入”但手工验证不成功,怎么回事
因为工具靠响应差异(状态码、延时、报错关键词)推测,而真实环境往往掩盖了这些信号——比如统一返回 500、错误日志不打到响应体、或数据库配置了 sql_mode=STRICT_TRANS_TABLES 导致非法输入直接被截断而非报错。
更可靠的验证方式:
- 用布尔盲注探针:
' AND 1=1--vs' AND 1=2--,观察业务逻辑是否变化(比如列表多一条/少一条、页面是否跳转) - 避免依赖报错回显,改查响应时间:
' AND SLEEP(3)--看接口是否延迟 - 确认数据库类型再选 payload:
PostgreSQL用pg_sleep(),MySQL用SLEEP(),SQL Server用WAITFOR DELAY
真正难搞的是那些参数进 LIKE、REGEXP、存储过程调用的地方——它们不走常规检测路径,但一旦出问题,权限可能比普通查询高得多。










