二次SQL注入需人工追踪“写入→存储→取出→拼接”数据链,静态分析grep定位字段在不同上下文的处理差异,如username注册时用prepare()插入、修改密码时却字符串拼接;sqlmap因无法关联跨请求payload生命周期而基本无效。

二次SQL注入无法靠单次请求扫描发现,必须人工追踪“写入→存储→取出→拼接”这条数据链。静态分析比动态探测更有效。
怎么用 grep 快速定位存储-触发链
别只盯着单个 SQL 语句,要找同一字段在不同上下文中的处理差异。比如 username 在注册时被 prepare() 插入,但在密码修改时又被拼进 UPDATE 字符串里。
- 先搜所有写入点:
grep -r "INSERT.*username\|UPDATE.*SET.*username" ./app/ - 再搜所有读取后拼接的地方:
grep -r "WHERE username = '.*\$username.*'\|\"username = '\$username'\"" ./app/ - 重点对比:第一次是否用了
bind_param()或addslashes(),第二次是否退化成"... '$username' ..." - ORM 场景下特别注意混用:比如
User::find(1)->username取出后,又塞进DB::statement("UPDATE ... WHERE name = '$name'")—— 这里就断掉了参数化保护
为什么 sqlmap 对二次注入基本无效
sqlmap 默认只测试当前请求参数能否直接改变响应,而二次注入的 payload 需要“存一次、等一次、再触发一次”,中间隔着数据库持久化和业务跳转。
- 它不会自动注册一个含
' OR '1'='1的用户名,再发起密码修改请求去激活 - 即使你手动分两步跑,
sqlmap --data也无法关联两次请求间的 payload 生命周期 - 真实案例中,80% 的二次漏洞在 sqlmap 报告里显示为“无注入”,因为探测流量根本没走到二次查询分支
- 误报率高:若后端对存储值做了
addslashes(),sqlmap 可能扫出“无害”的单引号,却漏掉真正触发点
哪些地方最容易被当成“可信数据”而跳过校验
修复不能只盯插入或查询任一端,中间环节的信任假设才是高危区。
- 缓存层绕过:从 Redis 读出的
username若未经校验直接拼 SQL,等同于从数据库直取——缓存只是另一层“存储”,不是可信源 - 日志回填场景:后台导出报表时,把数据库里的
nickname拼进统计 SQL,这个操作往往在运维脚本里,容易被忽略 - 数据库字段类型设为
VARCHAR(20)不代表安全——攻击者仍可存入admin'--这种 9 字符 payload - ORM 查询中混用原生 SQL:比如用
find()查用户,再用->toArray()提取username,最后拼进DB::statement(),这里就断掉了参数化保护
最麻烦的是那些“看起来安全”的中间态:数据从数据库取出、经缓存、进日志、被导出、再喂给另一个 SQL 查询——每一步都可能成为信任断点。检测时得一条链一条链手工捋,不能依赖工具自动覆盖。

















