单纯依靠全局关键词拦截来防范SQL注入攻击,这条路并不可行——不仅可靠性不足,还容易误伤正常的业务数据。真正有效的做法,是将参数化SQL作为默认的开发规范;拦截器只能充当临时补丁,而且必须配合解码、白名单、日志记录等操作才能发挥一定作用。下面具体剖析几个容易踩坑的细节。
在OnActionExecuting中读取参数时避免多次触发InputStream
直接遍历HttpContext.Request.Form或调用Request.ReadFormAsync()会消耗原始请求流,从而导致后续Controller无法接收数据(特别是JSON Body)。这是.NET Core中一个常见的静默失败点,开发者往往需要排查很久才能发现。
- 优先从
context.ActionArguments获取已经绑定的参数值,避免解析原始请求流 - 如果必须读取原始Query或Body:对于Query,使用
context.HttpContext.Request.QueryString.Value进行字符串解析;对于POST JSON,先调用context.HttpContext.Request.EnableBuffering(),再将Position重置为0 - 跳过ASP.NET的内置字段:
__RequestVerificationToken、__viewstate(大小写不敏感)——它们虽然包含'或--,但属于合法内容
在进行关键词检测前必须先解码再匹配
攻击者常用URL编码绕过,比如%27(单引号)、%6f%72(or),如果不进行解码就等于直接放行。这个细节容易被忽视,但后果非常严重。
- 对每个参数值执行
WebUtility.UrlDecode()以及WebUtility.HtmlDecode() - 使用正则表达式
b(select|union|drop|exec|xp_cmdshell)b并添加词边界,防止误拦截“Select”这样的昵称 - 关键词列表必须包含注释符号:
--、/*、#,否则admin'--这样的注入可以直接逃逸
哪些地方过滤器根本无法拦截?
当用户输入出现在SQL语法结构位置(而非值位置)时,字符串过滤完全无效。这里列出几个典型场景,每个都需要单独处理。
ORDER BY:无法加引号,参数化也失效 → 必须使用白名单校验,例如new[] { "name", "created_at" }.Contains(input)IN (:多个值需要拼接 → 采用) string.Join(",", userInputs.Select(x => $"'{SqlEscape(x)}'))手动转义(仅适用于极简场景)- 表名/列名动态拼接:
SELECT * FROM {tableName}→ 过滤器完全无法处理,只能依赖严格的白名单或禁止动态构造
真正关键的不是写了多少关键词,而是你是否确认:所有拼接SQL的地方都已迁移到SqlParameter或EF Core的FromSqlRaw(..., params);尚未迁移的接口,是否单独配置了日志、告警和人工复核?只要漏掉一个string.Format拼接,整个过滤器就形同虚设。简而言之:不要把赌注押在过滤器上,将参数化作为默认习惯才是根本之道。
