【xss防御】springboot项目如何防御XSS攻击

什么是 XSS

Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。

为了和 CSS 区分,这里把攻击的第一个字母改成了 X,于是叫做 XSS。

XSS 的本质是:恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。

而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。

在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并由浏览器执行,来完成比较复杂的攻击策略。

简单理解就是说,XSS是指攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被解析执行,从而达到恶意攻击用户的目的。XSS攻击人群是针对用户层面的攻击!

XSS漏洞分类

存储型XSS

存储型XSS,比如有些网站在个人信息编辑提交或发表文章、评论等地方,文本内容填写script代码,如果没有过滤或过滤不严,那么这些代码将储存到后端数据库服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie

 反射型XSS

非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面

 DOM型XSS

构造的URL参数不用发送到服务器端,可以达到绕过WAF、躲避服务端的检测效果。DOM-XSS漏洞是基于文档对象模型(Document Objeet Model,DOM)的一种漏洞,DOM-XSS是通过url传入参数去控制触发的,其实也属于反射型XSS。

攻击示例代码

<html>
    <head>
        <title>DOM Based XSS Demo</title>
        <script>
        function xsstest()
        {
        var str = document.getElementById("input").value;
        document.getElementById("output").innerHTML = "<img
        src='"+str+"'></img>";
        }
        </script>
    </head>
    <body>
    <div id="output"></div>
    <input type="text" id="input" size=50 value="" />
    <input type="button" value="submit" onclick="xsstest()" />
    </body>
</html>

XSS防护建议

  • 限制用户输入规定输入值的类型,例如年龄只能是int,name为字母数字组合。
  • 如果拼接 HTML 是必要的,就需要采用合适的转义库对数据进行html encode处理。
  • 过滤或移除特殊的html标签。
  • 过滤javascript事件的标签。
  • 前端渲染数据和视图分开(当然现在的前端框架vue已经是这样的机制了)

 springboot项目如何防御

pom文件添加依赖

      <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.18</version>
        </dependency>

添加filter过滤器包装request对象

@Slf4j
@WebFilter(filterName = "xssFilter", urlPatterns = "/*")
public class XssFilter implements Filter {

    @Autowired
    private CsrfUrlConfig csrfUrlConfig;


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

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        String enctype = request.getContentType();
        String requestURI = request.getRequestURI();
      

        //todo 通过配置指定url来过滤不经过xss过滤
    
         /**
         * 如果请求为上传附件的话,则不用XssHttpServletRequestWrapper来包
         */
        if (StringUtils.isNotBlank(enctype) && enctype.contains("multipart/form-data")) {
            filterChain.doFilter(request, servletResponse);
        } else {
            try {
                XssHttpServletRequestWrapper xssRequestWapper = new XssHttpServletRequestWrapper(request);
             
                filterChain.doFilter(xssRequestWapper, servletResponse);
            } catch (Exception ex) {
                log.error("xssWrap包装过滤处理异常:{}", ex);
                ResultExtMessage resultMessage = new ResultExtMessage();
                resultMessage.setSuccess(false);
                resultMessage.setCode("402");
                resultMessage.setMessage(ex.getMessage());
                resultMessage.setErrorDetailMessage(ex.getMessage());
                resultMessage.setMessagesAsString(ex.getMessage());
                httpResponse.setCharacterEncoding("UTF-8");
                httpResponse.setContentType("application/json; charset=utf-8");
                final PrintWriter writer = httpResponse.getWriter();
                writer.write(JSON.toJSONString(resultMessage));
                writer.flush();
                writer.close();
            }

        }

    }

    @Override
    public void destroy() {

    }
}
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }


    /**
     * 过滤request.getParameter的参数
     */
    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        if (!StrUtil.hasEmpty(value)) {
            value = StrUtil.trim(HtmlUtil.cleanHtmlTag(value));
        }
        return value;
    }


    /**
     * 过滤springmvc中的 @RequestParam 注解中的参数
     */
    @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 = StrUtil.trim(HtmlUtil.cleanHtmlTag(value));
                }
                values[i] = value;
            }
        }
        return values;
    }


    /**
     * 过滤from表单提交参数
     *
     * @return
     * @throws IOException
     */
    @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 = StrUtil.trim(HtmlUtil.cleanHtmlTag(value));
                    }
                    values[i] = value;
                }
                map.put(key, values);
            }
        }
        return map;
    }


    /**
     * 过滤header头参数
     *
     * @return
     * @throws IOException
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (!StrUtil.hasEmpty(value)) {
            value = StrUtil.trim(HtmlUtil.cleanHtmlTag(value));
        }
        return value;
    }

    /**
     * 过滤json请求
     *
     * @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();

        /**
         * 处理jsonArray的情况
         */
        String resultStr = null;
        //判断第一个字符是不是为[
        String bodyStr = body.toString();
        if (bodyStr.startsWith("[")) {
            List<Map<String, Object>> list = new ArrayList<>();
            JSONUtil.parseArray(bodyStr).stream().forEach(e -> {
                Map<String, Object> map = JSONUtil.parseObj(e);
                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, StrUtil.trim(HtmlUtil.cleanHtmlTag(val.toString())));
                        }
                    } else {
                        result.put(key, val);
                    }
                }
                list.add(result);
            });
            resultStr = JSONUtil.toJsonStr(list);
        } else {
            Map<String, Object> map = JSONUtil.parseObj(bodyStr);
            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, StrUtil.trim(HtmlUtil.cleanHtmlTag(val.toString())));
                    }
                } else {
                    result.put(key, val);
                }
            }
            resultStr = JSONUtil.toJsonStr(result);
        }
        ByteArrayInputStream bain = new ByteArrayInputStream(resultStr.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) {

            }
        };
    }


}

启动类添加注解扫描对应的filter

@ServletComponentScan(basePackages = {"com.xx.filter.xss"})

参考文章

前端安全系列(一):如何防止XSS攻击? - 掘金

跨站脚本攻击—XSS - 掘金

这一次,彻底理解XSS攻击 - 掘金

猜你喜欢

转载自blog.csdn.net/run_boy_2022/article/details/131519000