跨站脚本攻击(XSS)可以让攻击者在受害者的浏览器中执行恶意脚本来修改网页内容、将用户重定向到非法网站、伪造用户登录态、窃取用户的隐私信息、甚至还能给程序开个后门等等,所以不得不防。今天就来分享几种常用的防范XSS攻击的措施。
XSS攻击
可能上面说的不够直观,下面我们来看一下XSS攻击的方式。假设我们写了一个注册用户接口:
- POST /user
- Host: api.felord.cn
- {
- "userId" : 1001,
- "username" : "felord.cn",
- "type" : "\<script\>alert(document.cookie)\</script\>"
- }
这样的用户如果查询出来并被渲染到前端时,type字段的值很容易被当做脚本执行,这是就是一种常见的XSS攻击。胖哥在刚刚入行的时候就遇到过,有人利用XSS挂他自己的广告到我们的网站中来牟取利益。我们需要在应用中做一些防御措施。
防范XSS攻击的手段
下面就是我比较常用的手段。
X-XSS-Protection请求头
X-XSS-Protection 响应头是 IE,Edge,Chrome 和 Safari 的一个特性,当检测到跨站脚本攻击 (XSS) 时,浏览器将停止加载页面。
- # 0 表示禁止XSS过滤 1 表示开启XSS过滤
- X-XSS-Protection: 0
- X-XSS-Protection: 1
- # 启用XSS过滤。 如果检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
- X-XSS-Protection: 1; mode=block
- # 启用XSS过滤 (谷歌浏览器专用)。 如果检测到跨站脚本攻击,浏览器将清除页面并使用CSP report-uri指令的功能发送违规报告。
- X-XSS-Protection: 1; report=<reporting-uri>
大部分浏览器都支持这一特性。
浏览器兼容性
可以看得出X-XSS-Protection的兼容性还是很好的,不过它的保护性比较弱。默认情况下,Spring security 会自动添加此请求头。
CSP请求头
上面已经提到了CSP,全称Content-Security-Policy(内容安全策略),它也是以请求头的形式存在。它允许站点管理者控制用户代理能够为指定的页面加载哪些资源。除了少数例外情况,设置的政策主要涉及指定服务器的源和脚本结束点。这将帮助防止跨站脚本攻击(XSS)。它的控制粒度更细,它通过一系列的指令声明可以决定URL、多媒体资源、字体的加载策略、脚本的执行策略。具体可以查看Content-Security-Policy文档。
例如仅支持执行来自https://felord.cn的脚本:
- Content-Security-Policy: script-src https://felord.cn
目前主流的浏览器也都支持该特性。
支持CSP的浏览器
在Spring Security中我们可以这样配置它:
- httpSecurity.headers()
- .contentSecurityPolicy(“script-src https://felord.cn”)
编码过滤转义
除此之外我们还可以使用编码的形式来转义请求参数和响应体的字符来防止XSS攻击。这里会用到Spring提供的工具类org.springframework.web.util.HtmlUtils,当然Apache Commons也有类似的工具类。
- HtmlUtils.htmlEscape(String value)
利用上面这个方法我们可以针对性的编写HttpServletRequestWrapper的实现来对请求参数进行转义:
- import org.springframework.web.util.HtmlUtils;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletRequestWrapper;
- import java.util.stream.Stream;
- public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
- public XssHttpServletRequestWrapper(HttpServletRequest request) {
- super(request);
- }
- @Override
- public String getHeader(String name) {
- String value = super.getHeader(name);
- return HtmlUtils.htmlEscape(value);
- }
- @Override
- public String getParameter(String name) {
- String value = super.getParameter(name);
- return HtmlUtils.htmlEscape(value);
- }
- @Override
- public String[] getParameterValues(String name) {
- String[] values = super.getParameterValues(name);
- return values != null ? (String[]) Stream.of(values)
- .map(HtmlUtils::htmlEscape).toArray() :
- super.getParameterValues(name);
- }
- }
结合 Servlet Filter 或者Spring MVC 拦截器。
编写JSON序列化来实现对JSON返回的转义,例如Jackson中自定义XSS序列化
- public class XssStringJsonSerializer extends JsonSerializer<String> {
- @Override
- public Class<String> handledType() {
- return String.class;
- }
- @Override
- public void serialize(String value, JsonGenerator jsonGenerator,
- SerializerProvider serializerProvider) throws IOException {
- if (value != null) {
- jsonGenerator.writeString(HtmlUtils.htmlEscape(value));
- }
- }
- }
总结
今天介绍了几种常用的防止XSS攻击的方式,主要是涉及后端的。其实像一些现代的前端框架都支持将字符串变量转义,比如React的JSX。不过话又说回来,提高应用的安全的根本方法就在于降低攻击者的收益和提高攻击者的成本。
本文转载自微信公众号「码农小胖哥」,可以通过以下二维码关注。转载本文请联系码农小胖哥公众号。