filter不能替代参数化sql,仅作最后一道防线;遍历getparameternames()在文件上传或json请求中会因inputstream读取限制导致后续controller收不到数据;sqlvalidate()须先解码再匹配、加词边界、覆盖注释符及系统字段;仅动态sql拼接接口需启用filter;日志须记录url、参数名与解码值、ip及user-agent。

Filter不能替代参数化SQL,仅能作为最后一道防线;盲目全站扫描请求参数反而容易误杀合法输入、漏掉真正高危点。
为什么直接遍历getParameterNames()会出问题
老式JSP项目里常见写法是用request.getParameterNames()拿所有参数名,再逐个getParameter()取值校验。但这种做法在含文件上传(multipart/form-data)或JSON Body的请求中会失败——getParameter()底层会尝试解析整个Body,而Servlet容器只允许读一次InputStream,后续Controller或框架就收不到原始数据了。
- 对
application/x-www-form-urlencoded请求基本可用,但不兼容现代混合格式 - 遇到
Content-Type: application/json时,getParameter()始终返回null,过滤器形同虚设 - 若请求含中文或URL编码字符(如
%27代表单引号),未先解码就匹配,攻击者可轻松绕过
sqlValidate()里必须处理的三类绕过场景
关键词黑名单匹配看似简单,实际极易被绕过。以下规则必须写进sqlValidate()逻辑:
- 先调用
URLDecoder.decode(str, "UTF-8")和HtmlUtil.unescape(str)(或StringEscapeUtils.unescapeHtml4()),再转小写做匹配 - 正则必须加词边界:
\b(select|union|drop|exec)\b,否则"user_select"或"midnight"会被误杀 - 必须包含注释符号:
--、/*、#,否则admin'--直接逃逸 - 跳过ASP.NET风格的系统字段:
__VIEWSTATE、__RequestVerificationToken等,它们天然含+、=、/
哪些接口才该启用SQL注入Filter
全局无差别拦截是反模式。真正需要Filter保护的,只有那些明确存在动态SQL拼接的控制器入口,比如:
- 老系统中的通用查询接口:类似
DAdmin.GetList?table=user&where=id>1 - 后台导出功能:接受
order by字段名或group by列名作为参数 - 自定义报表SQL构造页:用户可输入部分WHERE条件字符串
其他普通CRUD接口,只要用了PreparedStatement或MyBatis的#{}占位符,Filter就纯属冗余——它既拦不住表名/列名注入,也增加无谓性能开销。
Filter日志必须记录的三个字段
光拦截不分析,等于没防。每次触发拦截,至少记下:
- 原始请求URL(含QueryString)
- 被拦截的参数名+解码后值(不是原始
getParameter()结果) - 客户端IP与User-Agent(用于识别是否为扫描器流量)
高频出现的绕过模式(如se%6Cect、sel/**/ect)要定期汇总,更新关键词库或推动对应接口改用白名单校验——这才是Filter该起的作用:暴露风险点,倒逼代码重构。











