全局拦截不能替代参数化查询,必须在 SqlCommand或EF Core层用@param占位符;OnActionExecuting中读取请求流需先EnableBuffering并重置位置;关键词检测须双重解码、词边界匹配且仅用于告警。

全局拦截不能替代参数化查询,它只能作为日志告警或临时补丁;真要防住SQL注入,必须在 SqlCommand 或 EF Core 查询层用 @param 占位符,否则拦截逻辑再严密也晚了。
为什么 OnActionExecuting 里查原始请求流会静默失败
很多人想在 OnActionExecuting 中读 HttpContext.Request.Body 或 Form 来做关键词扫描,但这样会把流消耗掉——后续 Controller 的 [FromBody] 绑定就收不到数据,且不报错,只返回空对象或 400。
- 必须先调
context.HttpContext.Request.EnableBuffering(),再用context.HttpContext.Request.Body.Position = 0重置位置 - 优先从
context.ActionArguments取值,它已是模型绑定后的干净对象,不碰原始流 - 若必须检查 Query 字符串,用
context.HttpContext.Request.QueryString.Value,它不消耗流 - 跳过 ASP.NET 内置字段:
__RequestVerificationToken、__viewstate(大小写不敏感),它们合法含'或--
关键词检测必须解码 + 词边界 + 注释符全覆盖
不 WebUtility.UrlDecode() 和 WebUtility.HtmlDecode() 就匹配,等于白忙——%27(单引号)、%6f%72(or)全漏过;正则没 \b 词边界,Select 昵称、union_all 字段名全被误杀。
- 关键词列表至少包含:
select、union、drop、exec、xp_cmdshell、--、/*、# - 检测前统一做两次解码:
WebUtility.HtmlDecode(WebUtility.UrlDecode(input)) - 正则模式用
\b(select|union|drop|exec)\b,加RegexOptions.IgnoreCase - 这种检测只适合记录+告警,不能替代参数化;必须配合日志记录原始参数、IP、路径
动态 SQL 结构位置根本拦不住,必须白名单硬控
模型绑定后拿到的 model.SortBy 或 model.TableName 是字符串,不是值——参数化对它们完全无效。你在 ORDER BY {model.SortBy} 里拼接,拦截器就算扫出 1=1 也没用,SQL 语法结构已经污染。
-
ORDER BY字段名:只允许new[] { "name", "email", "created_at" }.Contains(input) -
IN子句多个值:别拼"IN (" + string.Join(",", ids) + ")",改用.Where(x => model.Ids.Contains(x.Id))下推为参数化IN (@p0, @p1, ...) - 表名/列名拼接:拒绝所有非常量输入,或查配置表白名单,禁止任何运行时解析
-
LIKE模糊查询:用u.Name.Contains(model.Keyword)安全;手动拼"%" + model.Keyword + "%"再塞进FromSqlRaw,就得自己SqlEscape(),仍不如 LINQ 下推可靠
最常被忽略的一点:全局拦截器永远看不到 SqlCommand 构造过程,也插不进 EF Core 的 SQL 生成链路。它只能查到“用户传了什么”,查不到“代码最终拼出了什么”。真正危险的操作——比如 string.Format("SELECT * FROM {table}", input.Table) 或 FromSqlInterpolated——拦截器根本无能为力。

















