Filter不能替代参数化SQL,仅作为最后一道防线;应聚焦动态SQL拼接点、用白名单校验表名列名、结合日志告警推动重构。

Filter中直接拦截非法字符,但别只靠字符串匹配
全局过滤器能统一处理请求参数,但SqlFilter这类基于关键词黑名单的实现容易漏判或误杀。比如select出现在用户昵称“Select”里就被干掉;而绕过方式如se%6Cect(URL编码)、大小写混用、注释符拆分(sel/**/ect)都可能逃逸。真正该拦的是「参与SQL拼接」的参数,不是所有含or的字符串。
实操建议:
- 只对明确用于拼接SQL的控制器(如老系统中
DAdmin.GetList这种动态条件构造场景)启用过滤,避免全站无差别扫描 - 关键词列表必须包含
--、/*、#等注释符号,否则admin'--直接绕过 - 检测前先
HttpUtility.UrlDecode和HtmlDecode,否则编码后的%27(单引号)逃逸检测 - 正则匹配用
\b(select|union|drop)\b加词边界,减少误匹配
OnActionExecuting里如何安全读取Form和QueryString
直接遍历filterContext.HttpContext.Request.Form会触发多次Request.InputStream读取,导致后续模型绑定失败——这是.NET MVC 5常见坑。Form集合是只读的,反射改IsReadOnly属性属于危险操作,.NET Core已彻底移除该路径。
实操建议:
- 用
filterContext.ActionParameters获取已绑定的参数值,避开原始请求流读取 - 对
QueryString用filterContext.HttpContext.Request.QueryString.Value字符串解析,再正则检测 - 若必须读原始Body(如JSON POST),需提前调用
Request.EnableBuffering()并重置Position,否则Controller层收不到数据 - 跳过ASP.NET内置字段:
__VIEWSTATE、__EVENTVALIDATION、__RequestVerificationToken,它们含特殊字符但合法
为什么Filter校验不能替代参数化SQL
过滤器只能降低风险,不能消除SQL注入。当攻击者控制的是ORDER BY字段名、IN子句的值列表、或表名时,字符串过滤完全无效——这些位置根本不能加引号,也无法用参数化占位符。比如SELECT * FROM {tableName} WHERE id = @id中{tableName}若来自用户输入,过滤器拦不住sysusers; DROP TABLE users--。
实操建议:
- 过滤器仅作为最后一道防线,核心逻辑必须迁移到参数化:EF Core用
FromSqlRaw时显式传params,ADO.NET用SqlParameter - 动态表名/列名需白名单校验,例如
allowedColumns.Contains(userInput),而非模糊匹配 - 日志记录被拦截的请求,重点分析高频绕过模式,持续更新关键词库
- 对已知高危接口(如搜索、报表导出)单独加
[ValidateInput(false)]并手动校验,避免全局过滤误伤富文本
兼容性与性能隐患:Filter在.NET Core中的变化
.NET Core MVC的ActionFilterAttribute生命周期变了,OnActionExecuting执行时模型绑定已完成,但HttpContext.Request.Body默认不可重复读。老项目移植时,若Filter里还按.NET Framework方式读InputStream,会抛InvalidOperationException: Synchronous operations are disallowed。
实操建议:
- .NET Core中禁用同步读取,改用
await Request.BodyReader.ReadAsync()配合StreamReader - 避免在Filter里做耗时操作(如IO、正则全量扫描),否则拖慢所有请求;可异步预编译
RegexOptions.Compiled - Core中推荐用
IAuthorizationFilter替代ActionFilter做前置校验,权限过滤器优先级更高 - 生产环境务必关闭详细错误页,否则
OnActionExecuting抛异常会暴露内部路径和过滤逻辑
过滤器的真正价值不在“堵”,而在“标”——把可疑请求打上标记、记入审计日志、触发告警,逼着团队去重构那几处硬编码SQL。指望一个Filter包打天下,最后卡在ORDER BY动态字段上时,连日志都找不到突破口。

















