[xss defense] How the springboot project defends against XSS attacks

What is XSS

Cross-Site Scripting (cross-site scripting attack), referred to as XSS, is a code injection attack. Attackers inject malicious scripts into the target website so that they can run on the user's browser. Using these malicious scripts, attackers can obtain users' sensitive information such as cookies, SessionID, etc., thereby endangering data security.

In order to distinguish it from CSS, the first letter of the attack is changed to X, so it is called XSS.

The essence of XSS is that malicious code is not filtered and mixed with the normal code of the website; the browser cannot tell which scripts are trustworthy, causing the malicious script to be executed.

Since it is executed directly on the user's terminal, the malicious code can directly obtain the user's information, or use this information to impersonate the user to initiate attacker-defined requests to the website.

In some cases, due to input limitations, the injected malicious script is shorter. However, more complex attack strategies can be completed by introducing external scripts and executing them by the browser.

A simple understanding means that XSS means that an attacker inserts malicious Script code into a Web page. When a user browses the page, the Script code embedded in the Web will be parsed and executed, thereby achieving the purpose of maliciously attacking the user. The XSS attack crowd is an attack on the user level!

XSS vulnerability classification

Stored XSS

Stored XSS, for example, some websites fill in script codes in the text content when editing and submitting personal information or publishing articles, comments, etc. If there is no filtering or the filtering is not strict, then these codes will be stored in the back-end database server and the user will access the page. trigger code execution. This kind of XSS is more dangerous and can easily cause worms and cookie theft.

 Reflected XSS

Non-persistent, you need to trick the user into clicking the link to trigger the XSS code (there is no such page and content in the server), and it is generally easy to appear on the search page

 DOM type XSS

The constructed URL parameters do not need to be sent to the server, which can bypass WAF and avoid server detection. The DOM-XSS vulnerability is Document Objeet Modela vulnerability based on the Document Object Model (,DOM). DOM-XSS is triggered by passing in parameters through the URL. In fact, it is also a reflected XSS.

Attack sample code

<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 protection recommendations

  • Restrict user input to specify the type of input value. For example, age can only be int, and name can be an alphanumeric combination.
  • If splicing HTML is necessary, you need to use a suitable escape library to html encode the data.
  • Filter or remove special html tags.
  • Tags that filter javascript events.
  • The front-end rendering data is separated from the view (of course, the current front-end framework Vue already has this mechanism)

 How to defend the springboot project

Add dependencies to pom file

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

Add filter filter to wrap request object

@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) {

            }
        };
    }


}

Add annotations to the startup class to scan the corresponding filter.

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

Reference article

Front-end security series (1): How to prevent XSS attacks? - Nuggets

Cross-site scripting attack - XSS - Nuggets

This time, thoroughly understand XSS attacks - Nuggets

Guess you like

Origin blog.csdn.net/run_boy_2022/article/details/131519000