抵御即跨站脚本(XSS)攻击

Posted 一菜一汤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了抵御即跨站脚本(XSS)攻击相关的知识,希望对你有一定的参考价值。

1、问题描述

什么是XSS?

  • 通过网页代码的漏洞,注入恶意指令到网页,当用户加载并执行攻击者恶意制造的网络程序,通常是javascript,但实际上也可以包括Java、 VBScript、ActiveX、 Flash 或者甚至是普通的html
  • 攻击者可以通过这种方式获取到一些私密权限,token,cookie等重要信息,冒充客户登录
  • 例如用户在发帖或者注册的时候,在文本框中输入 ,这段代码 如果不经过转义处理,而直接保存到数据库。将来视图层渲染HTML的时候,把这段代码输出到 页面上,那么

如何让避免这种XSS攻击
对用户输入信息进行转义(Hutool工具包为我们提供了HtmlUtil可以解决此类问题)

2、代码

问题?

  • 如何去转义用户输入信息
    • 过滤器,可以继承于HttpServletRequest的接口
      • 但是由于这个接口定义的方法太多,一一实现比较麻烦,因此我们有更好的选择
  • 更好的选择
    • 自定义请求类的方式
      • 继承HttpServletRequestWrapper父类

JavaEE规范还定义 了 HttpServletRequestWrapper ,这个类是请求类的包装类,用上了装饰器模式。不得不说这里 用到的设计模式真的非常棒,无论各家应用服务器厂商怎么去实现 HttpServletRequest 接口, 用户想要自定义请求,只需要继承 HttpServletRequestWrapper ,对应覆盖某个方法即可,然后 把请求传入请求包装类,装饰器模式就会替代请求对象中对应的某个方法。用户的代码和服务器 厂商的代码完全解耦,我们不用关心 HttpServletRequest 接口是怎么实现的,借助于包装类我 们可以随意修改请求中的方法。

上代码

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {


    public XssHttpServletRequestWrapper(HttpServletRequest request) {
       super(request);
    }

    @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(ArrayUtil.isNotEmpty(values)){
            for (int i = 0; i < values.length; i++) {
                String value =values[i];
                if(!StrUtil.hasEmpty(values[i])){
                    value = HtmlUtil.filter(value);
                }
                values[i] = value;
            }
        }
        return values;
    }

    @Override
    public Map<String, String[]> getParameterMap() {

        Map<String, String[]> parameterMap = super.getParameterMap();
        LinkedHashMap<String, String[]> map = new LinkedHashMap<>();
        if(CollUtil.isNotEmpty(parameterMap)){
            for (String key: parameterMap.keySet()) {
                String[] values = parameterMap.get(key);
                    if(ArrayUtil.isNotEmpty(values)){
                        for (int i = 0; i < values.length; i++) {
                            String value =values[i];
                            if(!StrUtil.hasEmpty(values[i])){
                                value = HtmlUtil.filter(value);
                            }
                            values[i] = value;
                        }
                    }
                    map.put(key,values);
            }
        }
        return map;
    }

    @Override
    public String getHeader(String name) {
        String header = super.getHeader(name);
        if(StrUtil.isNotBlank(header)){
           header = HtmlUtil.filter(header);
        }
        return header;
    }
      @Override
    public ServletInputStream getInputStream() throws IOException {
        InputStream in = super.getInputStream();
        StringBuffer body = new StringBuffer();
        InputStreamReader reader = new InputStreamReader(in, Charset.forName("UTF-8"));
        BufferedReader buffer = new BufferedReader(reader);
        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> resultMap = new HashMap(map.size());
        for (String key : map.keySet()) {
            Object val = map.get(key);
            if (map.get(key) instanceof String) {
                resultMap.put(key, HtmlUtil.filter(val.toString()));
            } else {
                resultMap.put(key, val);
            }
        }
        String str = JSONUtil.toJsonStr(resultMap);
        final ByteArrayInputStream bain = new ByteArrayInputStream(str.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 listener) {
            }
        };
    }
}

自定义过滤器

为了让刚刚定义的包装类生效,我们还要在 com.example.emos.wx.config.xss 中创建 XssFilter 过滤器。过滤器拦截所有请求,然后把请求传入包装类,这样包装类就能覆盖所有请 求的参数方法,用户从请求中获得数据,全都经过转义。

@WebFilter(urlPatterns = "/*")  //过滤所有请求路径
public class XssFilter implements Filter {


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        XssHttpServletRequestWrapper servletRequestWrapper = new XssHttpServletRequestWrapper(
            (HttpServletRequest) servletRequest);
        filterChain.doFilter(servletRequestWrapper,servletResponse);

    }

    @Override
    public void destroy() {

    }
}

给主启动类添加@ServletComponentScan注解

img

以上是关于抵御即跨站脚本(XSS)攻击的主要内容,如果未能解决你的问题,请参考以下文章

XSS跨站脚本攻击介绍

HostOnly Cookie

Xss Csrf DDOS sql注入及防范

Django XSS攻击

如何利用XSS脚本攻击绕过WAF防火墙

XSS简介