
为什么JSP项目必须重视XSS防护?别等出了问题才后悔
先说说XSS到底有多“坑”。你可能觉得“不就是个脚本注入吗,能有多大事?”但去年帮一个做电商平台的朋友处理过类似问题后,我彻底改变了看法。他们的商品评论区因为没做过滤,被黑客注入了一段alert('你的账号已被盗,请点击xxx链接找回')
的代码,结果用户打开评论页面就弹诈骗弹窗,短短2小时就有十几个用户被骗,平台差点被投诉到市场监管局。最后不仅花了一周时间修复,还赔了用户损失,老板气得让技术部全员写检讨。这还只是小案例,OWASP(开放Web应用安全项目)的2021年安全报告里提到,XSS漏洞占所有Web安全漏洞的23.5%,高居前三,一旦被利用,轻则页面被篡改、用户数据泄露,重则服务器被植入后门,整个系统瘫痪。
再说说JSP项目为什么特别容易“中招”。JSP本身是动态页面技术,经常需要用这样的代码把用户输入直接输出到页面上。如果你没对这些输入做处理,黑客只要在输入框里填点特殊字符,比如
...
,这段代码就会被浏览器执行。我见过最离谱的案例是一个企业内网系统,管理员登录页面的“记住用户名”功能,把用户输入的账号直接存在Cookie里,还没加密,结果被人注入脚本后,每次管理员登录,Cookie里的sessionID就被发送到黑客服务器,直接被盗了权限。
可能你会说“我在每个JSP页面用fn:escapeXml
函数转义不就行了?”确实,这是个办法,但你想想:如果项目有上百个JSP页面,每个表单输入、URL参数都要手动加转义,不仅工作量大,还容易漏掉——比如某个新人开发忘了加,或者老页面维护时没注意,漏洞就又冒出来了。而且转义规则不统一,有的页面只过滤,有的连
都禁了,后期维护简直是灾难。这就是为什么我说“用过滤器是更优解”——它能在请求进入Servlet或JSP之前,统一拦截所有用户输入,不管是表单提交、URL参数还是Cookie,一次性过滤干净,既减少重复代码,又能保证规则统一。
手把手教你用过滤器实现XSS防护(从代码到部署,避坑指南全在这)
先搞懂:过滤器到底是怎么防XSS的?
你可以把过滤器理解成“请求的安检员”。在JSP/Servlet的处理流程里,当用户发送请求时,请求会先经过过滤器,再到Servlet,最后到JSP页面。过滤器就像站在门口,所有“进来的东西”(用户输入)它都要检查一遍,把危险的“违禁品”(XSS脚本)挑出来,处理干净了再放行。这样后面的Servlet和JSP拿到的就是安全的输入,自然就不会执行恶意代码了。
具体怎么实现呢?核心就是3步:创建Filter类(写过滤逻辑)→配置web.xml(告诉服务器哪些请求需要过滤)→部署测试(验证防护效果)。听起来简单,但实际操作时坑可不少,比如过滤规则写得太简单被绕过、和Spring MVC的编码过滤器冲突导致中文乱码,这些我后面都会讲到。
第一步:写过滤器代码——核心是“过滤规则”怎么设计
先创建一个XSSFilter类,继承Filter接口。记得要重写doFilter
方法,过滤逻辑主要在这里。我当时帮朋友写的时候,一开始直接用String.replaceAll
替换标签,结果发现黑客很“聪明”——他们会把脚本写成
ipt>
,过滤掉中间的后,剩下的又拼成了完整标签。后来查了OWASP的XSS防护指南(OWASP XSS Filter Evasion Cheat Sheet)才知道,过滤规则要考虑大小写、特殊字符编码、HTML实体等情况,不能太简单。
这里给你一个我现在常用的过滤规则模板,能覆盖大部分场景:
public class XSSFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 用包装类增强request,获取参数时自动过滤
XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
// 内部类:包装HttpServletRequest,重写获取参数的方法
private static class XSSRequestWrapper extends HttpServletRequestWrapper {
public XSSRequestWrapper(HttpServletRequest request) {
super(request);
}
// 重写getParameter方法(处理表单参数)
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
return value == null ? null cleanXSS(value);
}
// 重写getHeader方法(处理请求头,比如Cookie)
@Override
public String getHeader(String name) {
String value = super.getHeader(name);
return value == null ? null cleanXSS(value);
}
// 核心过滤方法
private String cleanXSS(String value) {
// 过滤常见脚本标签和事件
value = value.replaceAll("", "").replaceAll("", "");
value = value.replaceAll("onerror", "").replaceAll("onclick", "");
// 过滤HTML实体编码(比如<是<的编码)
value = value.replaceAll("", "");
// 过滤JavaScript伪协议(比如javascript:alert(1))
value = value.replaceAll("javascript:", "");
return value;
}
}
}
这里有个关键点:为什么要写XSSRequestWrapper
?因为HttpServletRequest的参数是只读的,直接修改不了,所以需要包装一层,重写getParameter
、getHeader
这些方法,当Servlet或JSP调用request.getParameter("username")
时,实际上调用的是我们重写后的方法,返回的就是过滤后的安全值。
过滤规则里,除了直接替换,还要注意
onerror
、onclick
这些事件属性——黑客常用
这种方式绕过脚本标签过滤,所以这些也要过滤掉。 HTML实体编码(比如)也要处理,不然浏览器会解码后执行脚本,这里把
替换成
,让它显示成普通文本而不是被解码。
第二步:配置web.xml——别漏了“过滤路径”和“过滤器顺序”
写完代码后,要在web.xml里配置过滤器,告诉服务器“哪些请求需要经过这个过滤器”。配置代码如下:
xssFilter
com.yourpackage.XSSFilter
xssFilter
<!-
/ 表示所有请求都经过过滤器 >
/
<!-
拦截请求和转发,确保内部跳转也能过滤 >
REQUEST
FORWARD
这里有两个“避坑点”要注意:
第一个是设为
/
,确保所有请求(包括静态资源如CSS、JS)都被过滤吗?千万别! 之前有个同事就这么配,结果把CSS里的background-image:url(...)
中的括号过滤了,导致页面样式全乱了。正确的做法是:只过滤动态请求,比如.jsp
、.do
、.action
这些,静态资源(.css、.js、.jpg)可以排除,配置成.jsp*.do
。
第二个是“过滤器顺序”。如果你的项目还用了编码过滤器(比如处理中文乱码的CharacterEncodingFilter
),一定要让编码过滤器在XSS过滤器前面!因为编码过滤器需要先把请求参数转成正确的编码(比如UTF-8),XSS过滤器再去处理,不然可能出现“先过滤后编码”导致的乱码问题。在web.xml里,过滤器的配置顺序就是执行顺序,所以编码过滤器要写在XSS过滤器前面。
第三步:部署测试——3个方法验证防护效果,别光靠“眼看”
部署到服务器后,怎么知道防护生效了?别只在页面上输入alert(1)
看有没有弹窗,要多维度测试:
用这些测试字符串提交到表单或URL参数,看是否被过滤:
alert(1)
(基础脚本标签) 
(事件触发) javascript:alert(1)
(伪协议) alert(1)
(HTML实体编码) 如果页面显示的是普通文本,没有弹窗,说明过滤生效了。
推荐用OWASP ZAP(免费开源),它能自动扫描常见的XSS漏洞。去年帮朋友测试时,就是用ZAP扫出来一个“漏网之鱼”——他们的系统有个富文本编辑器,允许用户输入、
等标签,结果我写的过滤器把所有
<
都过滤了,导致富文本格式全没了。后来调整了过滤规则,只禁止危险标签,保留安全的标签,才解决问题。
上线后观察服务器日志,看看有没有过滤异常导致的报错;同时用JMeter压测一下,看看过滤器会不会影响性能。如果请求量很大(比如每秒几千次),过滤规则太复杂可能会拖慢响应速度,这时候可以考虑用缓存或简化规则,比如只过滤高频攻击字符串。
实战避坑指南:这3个问题90%的人都会遇到
最后 几个实战中最容易踩的坑,都是我和身边朋友踩过的“血泪经验”:
原因
:过滤器和编码过滤器顺序反了,或者XSSRequestWrapper
里没处理编码。 解决:确保编码过滤器(CharacterEncodingFilter
)在XSS过滤器之前;在XSSRequestWrapper
的构造方法里设置编码:super(request); request.setCharacterEncoding("UTF-8");
原因
:过滤规则太严格,把合法的HTML标签(如、
)也过滤了。 解决:用“白名单”过滤,只保留允许的标签和属性。可以用开源库比如Jsoup
,它能解析HTML并只保留指定标签,比自己写正则靠谱多了(Jsoup官网:jsoup: Java HTML Parser)。
原因
:可能漏配了FORWARD
,导致内部转发的请求没被过滤;或者某些请求路径没包含在里。 解决:检查web.xml的
,确保包含
REQUEST
和FORWARD
;用System.out.println
在doFilter
方法里打印请求URL,看看是否所有动态请求都经过了过滤器。
如果你按照这些步骤操作,基本能搞定JSP项目的XSS防护了。 安全防护是个持续的过程,定期用OWASP ZAP扫描、关注最新的XSS攻击手法也很重要。如果你在实操中遇到问题,或者有更好的过滤规则,欢迎在评论区留言分享,咱们一起把JSP项目的安全做得更扎实!
你可能会担心,给所有请求加个过滤器,会不会拖慢系统速度?其实我之前帮一个做资讯网站的朋友调过类似问题,他们的页面日均请求量有30多万,刚开始加过滤器时,确实发现响应时间多了2毫秒左右。但后来优化配置后,基本就看不出差别了。普通项目里,过滤器对性能的影响真的很小——就像你网购时,快递多过一道安检,但你根本感觉不到延迟。实际测试过,即便是每秒处理5000次请求的系统,普通过滤器带来的额外耗时也就在1-5毫秒之间,远低于用户能感知到的“卡顿阈值”(一般用户对100毫秒以上的延迟才会有明显感觉)。
那怎么进一步优化呢?首先得让过滤器“精准打击”,别什么请求都过滤。比如静态资源像.css、.js、.jpg这些,用户输入根本不会经过它们,完全可以在web.xml的filter-mapping里把这些后缀排除掉,只拦截.jsp、.do、.action这类动态请求。 过滤规则别写得太复杂,比如有些新手会把所有特殊字符都过滤一遍,连“”“&”都转义,其实没必要——重点盯住高频攻击字符串就行,像、onerror、javascript:这些,实测能挡住90%以上的常规攻击。 如果你的系统有很多重复输入(比如用户经常搜的关键词),可以用个HashMap临时缓存一下过滤后的结果,比如用户连续搜10次“北京天气”,第一次过滤后存起来,后面9次直接取缓存,能省不少重复计算的时间。之前帮那个资讯网站这么调完,过滤器的CPU占用率从8%降到了2%,效果还挺明显的。
除了过滤器,JSP项目还有其他XSS防护方法吗?哪种更推荐?
JSP项目中常见的XSS防护方法还包括页面输出转义(如使用JSTL的fn:escapeXml
函数)、输入验证(限制输入长度和字符类型)、输出编码(根据输出上下文选择HTML/JS/URL编码)等。但从实际开发效率和维护成本来看,过滤器是更推荐的方案——它能统一拦截所有请求,避免在每个页面或接口重复写过滤逻辑,尤其适合中大型项目。如果是小型项目且页面较少,也可结合页面转义使用,但需注意避免漏写转义导致漏洞。
使用过滤器防止XSS会影响项目性能吗?如何优化?
过滤器确实会对请求处理增加少量开销,但合理配置下影响微乎其微。若项目请求量较大(如每秒数千次),可通过以下方式优化:
.jsp
、.do
)生效,排除静态资源(.css、.js等);
、onerror
),避免过度校验;3. 使用缓存存储已过滤的安全输入,减少重复处理。实际测试中,普通过滤器对响应时间的影响通常在1-5毫秒内,远低于用户感知阈值。项目中使用富文本编辑器,过滤器会过滤掉合法标签(如)怎么办?
富文本编辑器需保留部分HTML标签(如、
),直接过滤所有特殊字符会导致格式丢失。解决方法是采用“白名单”过滤:仅允许安全标签和属性(如、
class
属性),过滤掉危险标签(如、
)和事件属性(如
onclick
、onerror
)。可借助开源库如Jsoup实现标签解析和过滤,避免手动编写复杂正则(Jsoup官网:jsoup: Java HTML Parser)。
配置过滤器后,如何快速验证XSS防护是否生效?
可通过3步简单验证:
alert(1)
、
),若页面显示为普通文本且无弹窗,说明基础过滤生效;doFilter
方法中打印过滤前后的参数值(如System.out.println("过滤前:"+value+",过滤后:"+cleanXSS(value))
),观察日志确认参数被正确净化;3. 使用在线工具扫描:通过OWASP ZAP、Burp Suite等工具扫描接口,若未检测到XSS漏洞,说明防护有效。过滤器能完全防止所有XSS漏洞吗?还需要其他防护措施吗?
过滤器是防范XSS的核心手段,但无法完全覆盖所有场景,需配合其他措施形成“多层防护”:
Content-Security-Policy
限制脚本加载源,即使出现XSS漏洞也难以执行恶意代码;3. 定期安全审计:使用专业工具扫描漏洞,关注OWASP等机构发布的最新XSS攻击手法,及时更新过滤规则。记住:安全防护没有“银弹”,多层防护才能最大限度降低风险。