唯一可靠修复方式是全面使用SqlParameter参数化查询替代所有字符串拼接,严查ExecuteNonQuery/ExecuteReader调用点,显式指定SqlDbType与长度,禁用动态表名拼接和存储过程内EXEC,连接字符串加Charset=utf8防宽字节注入。

必须逐点替换所有动态拼接逻辑,用参数化查询兜底值,白名单校验标识符,禁用任何EXEC(@sql)或${}式拼接——没有“批量修复脚本”能绕过这个事实。
查全所有动态SQL执行点,别信“只在登录页有”
老项目里最危险的不是明面上的登录SQL,而是后台导出Excel、统计报表、调试接口、甚至某个aspx.cs里临时写的ExecuteNonQuery调用。这些地方往往多年没人动过,但攻击者扫一遍Request.QueryString["id"]就能命中。
- 全局搜索关键词:
ExecuteNonQuery、ExecuteReader、ExecuteScalar、mysqli_query(、mysql_query(、->query(、sp_executesql、EXEC( - 特别盯住
Request["xxx"]:它会从QueryString/Form/Cookies/ServerVariables里自动回溯查找,比单独查QueryString或Form更宽 - MyBatis项目重点搜
${,不是#{——前者等于直接开后门
参数化必须三要素齐备,缺一不可
只写new SqlParameter("@name", value)不加进Parameters集合,或只传占位符不声明@params字符串,都等于没做。数据库不会报错,只会默默返回空结果,让你误以为修好了。
- ASP.NET中:
cmd.Parameters.Add("@uid", SqlDbType.Int).Value = id,不能只用AddWithValue,否则类型推断错误会导致索引失效 - SQL Server存储过程中用
sp_executesql时,必须同时提供三段:@sql(含@param占位符)、@params(如N'@cat NVARCHAR(50)')、@cat = @userInput(实际传值) - Java PreparedStatement必须保证
setXxx()顺序和?位置严格一致,错一位就可能把密码塞进用户名字段
表名、列名、ORDER BY这些根本不能参数化
你没法对SELECT * FROM 后面的tableName做SqlParameter,也没法给ORDER BY ?传字段名——数据库语法根本不允许。这类地方拼接即高危,参数化救不了。
- 白名单是唯一选项:
if (!Arrays.asList("user", "order").contains(tableName)) throw new IllegalArgumentException(); - SQL Server可用
QUOTENAME(@tableName)包裹,但前提是@tableName已通过白名单校验,否则QUOTENAME只是帮你把恶意字符串转成带括号的合法标识符,依然执行 - PHP中
LIMIT值不能直接用?绑定(部分旧驱动不支持),得用min(max((int)$_GET['limit'], 10), 100)硬限范围
存储过程不是安全区,反而可能是高危放大器
很多老系统以为“用了存储过程=防注入”,结果过程体内又写了EXEC('SELECT * FROM ' + @table),或者接收参数后直接喂给xp_cmdshell。这类过程常以高权限运行,一旦被穿透,危害远超普通SQL。
- 逐个检查所有存储过程中是否含:
EXEC(、EXEC sp_executesql、EXEC('、sp_prepare - 禁用
xp_cmdshell,除非业务强依赖,且已显式启用(sp_configure 'show advanced options', 1)并限定执行账号权限 - 过程参数传入前仍需白名单校验,不能只靠“内部调用”放松警惕——权限提升后,后台管理入口就是最大攻击面
最容易被忽略的是:参数化只保护“值”,不保护“结构”。哪怕你100%用了SqlParameter,只要SQL字符串里出现FROM " + tableName或ORDER BY " + sortField,漏洞就还在。这类问题不会报错,也不会触发WAF规则,只能靠人工逐行确认。

















