type
Post
status
Published
date
Oct 10, 2021
slug
summary
在过滤器将请求类进行包装,并覆盖 http 参数处理方法方式实现参数转义的方式对可能的 XSS 脚本代码过滤
tags
项目方案
category
技术分享
icon
password
XSS 攻击介绍
XSS 就是一段可以被网页程序执行的恶意代码指令,通常是JavaScript,但实际上也可以包括 Java、 VBScript、ActiveX、 Flash 或者甚至是普通的HTML。
攻击者通过 XSS 可以获得比较高的权限来执行一些操作,例如获取私密网页内容、会话和cookie等各种内容,对应用安全造成影响。
- 例如用户在发帖或者注册的时候,在文本框中输入
<script>alert('xss')</script>,这段代码如果不经过转义处理,而直接保存到数据库。将来视图层渲染HTML的时候,把这段代码输出到页面上,那么<script>标签的内容就会被执行。
- 通常情况下,我们登陆到某个网站。如果网站使用
HttpSession保存登陆凭证,那么SessionId会以Cookie的形式保存在浏览器上。如果黑客在这个网页发帖的时候,填写的JavaScript代码是用来获取Cookie内容的,并且把Cookie内容通过Ajax发送给黑客自己的电脑。于是只要有人在这个网站上浏览黑客发的帖子,那么视图层渲染HTML页面,就会执行注入的XSS脚本,于是你的Cookie信息就泄露了。黑客在自己的电脑上构建出Cookie,就可以冒充已经登陆的用户。
- 即便很多网站使用了JWT,登陆凭证(
Token令牌)是存储在浏览器上面的。所以用XSS脚本可以轻松的从Storage中提取出Token,黑客依然可以轻松的冒充已经登陆的用户。
所以避免XSS攻击最有效的办法就是对用户输入的数据进行转义,然后存储到数据库里面。等到视图层渲染HTML页面的时候。转义后的文字是不会被当做JavaScript执行的,这就可以抵御XSS攻击。
请求包装类
自定义请求类可以考虑继承
HttpServletRequest 或 父类 HttpServletRequestWrapper但
HttpServletRequest 的抽象方法过多,继承父类 HttpServletRequestWrapper 是更好的选择- HttpServletRequestWrapper 是请求类的包装类,使用了装饰器模式,无论各家服务器厂商如何实现
HttpServletRequest接口,用户想要自定义请求只需要继承HttpServletRequestWrapper,对应覆盖某个方法即可
- 把请求传入包装类,装饰器模式就护器代替请求对象中某个对应方法,实现和原有实现的解耦
- 使用 Hutool 工具类中的 HtmlUtil 进行 XSS 转义
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.0</version> </dependency>
/** * 防御 XXS 攻击的 HttpServletRequest 装饰类 */ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { //声明构造器接收传入的请求对象 public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } /** * 覆盖getParameter方法并转义 */ @Override public String getParameter(String name) { //原始数据 String value = super.getParameter(name); if (!StrUtil.hasEmpty(value)) { //请求方法转义变为纯文本并覆盖 value = HtmlUtil.filter(value); } return value; } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values != null) { for (int i = 0; i < values.length; i++) { String value = values[i]; if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } values[i] = value; } } return values; } @Override public Map<String, String[]> getParameterMap() { Map<String, String[]> parameters = super.getParameterMap(); LinkedHashMap<String, String[]> map = new LinkedHashMap(); if (parameters != null) { for (String key : parameters.keySet()) { String[] values = parameters.get(key); for (int i = 0; i < values.length; i++) { String value = values[i]; if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } values[i] = value; } map.put(key, values); } } return map; } @Override public String getHeader(String name) { String value = super.getHeader(name); if (!StrUtil.hasEmpty(value)) { value = HtmlUtil.filter(value); } return value; } /** * 覆盖 getInputStream * SpringMVC通过这个方法来提取参数的数据封装对象 * * @return * @throws IOException */ @Override public ServletInputStream getInputStream() throws IOException { InputStream in = super.getInputStream(); InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8")); BufferedReader buffer = new BufferedReader(reader); StringBuffer body = new StringBuffer(); String line = buffer.readLine(); while (line != null) { body.append(line); line = buffer.readLine(); } buffer.close(); reader.close(); in.close(); Map<String, Object> map = JSONUtil.parseObj(body.toString()); Map<String, Object> result = new LinkedHashMap<>(); for (String key : map.keySet()) { Object val = map.get(key); if (val instanceof String) { if (!StrUtil.hasEmpty(val.toString())) { result.put(key, HtmlUtil.filter(val.toString())); } } else { result.put(key, val); } } String json = JSONUtil.toJsonStr(result); ByteArrayInputStream bain = new ByteArrayInputStream(json.getBytes()); return new ServletInputStream() { @Override public int read() throws IOException { return bain.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
过滤器配置
过滤器拦截所有请求,然后把请求传入包装类,这样包装类就能覆盖所有请求的参数方法,用户从请求中获得数据,全都经过转义
/** * Xss 防御过滤器 * 拦截请求并封装为 wrapper 对象 */ @WebFilter(urlPatterns = "/*") public class XssFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; XssHttpServletRequestWrapper wrapper = new XssHttpServletRequestWrapper(request); filterChain.doFilter(wrapper, servletResponse); } @Override public void destroy() {} }
配置完后,需要在 SpringBootApplication 主启动类上开启 @ServletComponentScan 注解,使得 @WebFilter 配置的过滤器能够自动注册
Relate Posts