中间件不能拦截SQL注入,因其无法接触SQL语句本身;应通过路由约束、查询长度限制等做请求结构校验;SQL注入防护必须在模型绑定后、执行前,用白名单校验动态表名/列名并禁用字符串拼接。

中间件不能直接拦截SQL注入,别往这个方向堆逻辑
中间件层根本接触不到 SQL 语句本身——SqlCommand 或 FromSqlRaw 是在控制器、服务或仓储里才构造的。你在 UseMiddleware 里试图读 HttpContext.Request.Body 并匹配 UNION、--,既漏报(如 URL 编码后的 %27OR%201%3D1),又误杀(用户昵称含 select、字段名叫 union_status)。更关键的是:这种检测发生在模型绑定前,你拿到的是原始字节流,而真正参与 SQL 构建的,是绑定后、经验证的 model.SortBy 或 model.Ids ——中间件根本不知道它们长什么样。
真正该放中间件里的,只有请求结构校验
中间件能且只应做“请求是否合法”的粗筛,不是“SQL 是否危险”的细判。重点守住三类入口:
- 路由参数必须带约束:
{id:int}、{slug:regex(^\w{{3,20}}$)},非法值直接 404,不进 MVC 管道 - Query 字符串长度限制:用
app.Use((ctx, next) => { if (ctx.Request.QueryString.Value.Length > 2048) return ctx.Response.StatusCode = 400; return next(); })拦截超长恶意载荷 - 跳过 ASP.NET 内置字段检测:对
__RequestVerificationToken、__viewstate(大小写不敏感)不做任何关键词扫描,否则登录页直接 500
模型绑定后、SQL 执行前,才是拦截真时机
SQL 注入防护必须卡在数据已结构化、但 SQL 还未拼接的窗口。这只能在 OnActionExecuting 或自定义 ActionFilter 里做:
- 从
context.ActionArguments取值,不是Request.Form或Request.Query—— 后者会二次读流,导致后续控制器收不到 JSON - 对动态排序字段(如
model.OrderBy)强制白名单:new[] { "name", "email", "created_at" }.Contains(input?.ToLowerInvariant()) - 对
IN子句数组(如model.Statuses),禁止拼字符串,改用.Where(x => model.Statuses.Contains(x.Status))让 EF Core 自动参数化 - 若必须调用
FromSqlRaw,确保第二个参数是new object[] { param1, param2 },绝不用FromSqlInterpolated或AddWithValue
最常被忽略的点:表名/列名拼接根本无法参数化
哪怕你把所有值都包进 @p0,只要写了 FROM {model.TableName} 或 ORDER BY {model.SortColumn},参数化就彻底失效。这类语法结构位(syntax position)必须硬隔离:
- 白名单必须是编译期确定的字符串数组,不能查数据库配置表(那表本身可能被注入)
- 拒绝未知值:遇到
model.TableName == "users; DROP TABLE logs--",直接context.Result = new BadRequestObjectResult("Invalid table name") - 不要试图“转义”表名:SQL Server 的
QUOTENAME()只防单引号,不防;、--、/*,且仅适用于 T-SQL 层,EF Core 不认

















