SpringBoot项目XSS攻击过滤防御实现

一、先来个简介#
什么是XSS?

百度百科的解释: XSS又叫CSS (Cross Site Script) ,跨站脚本攻击。它指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意用户的特殊目的。

它与SQL注入攻击类似,SQL注入攻击中以SQL语句作为用户输入,从而达到查询/修改/删除数据的目的,而在xss攻击中,通过插入恶意脚本,实现对用户游览器的控制,获取用户的一些信息。

二、XSS分类#
xss攻击可以分成两种类型:

1.非持久型攻击
2.持久型攻击

非持久型xss攻击:顾名思义,非持久型xss攻击是一次性的,仅对当次的页面访问产生影响。非持久型xss攻击要求用户访问一个被攻击者篡改后的链接,用户访问该链接时,被植入的攻击脚本被用户游览器执行,从而达到攻击目的。

持久型xss攻击:持久型xss,会把攻击者的数据存储在服务器端,攻击行为将伴随着攻击数据一直存在。

也可以分成三类:

反射型:经过后端,不经过数据库

存储型:经过后端,经过数据库
三、代码走起

先加pom文件加上依赖

<!-- xss过滤组件 -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.4</version>
        </dependency>
        

1.首先是要写个过滤器的包装类,这也是实现XSS攻击过滤的核心代码


```java
package com.jgtl.smurfs.common.filter;


import com.alibaba.fastjson.JSON;
import com.jgtl.smurfs.common.util.IPUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;


/**
 * <code>{@link XssHttpServletRequestWrapper}</code>
 *
 * @author
 */

public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final Logger log = LoggerFactory.getLogger(getClass());


    private static String key = "and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|+|$|..";
    private static Set<String> notAllowedKeyWords = new HashSet<String>(0);
    private static String replacedString = "INVALID";

    static {
        String keyStr[] = key.split("\\|");
        for (String str : keyStr) {
            notAllowedKeyWords.add(str);
        }
    }

    private String currentUrl;
    private String currentIp;


    /**
     * @param request
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        currentUrl = request.getRequestURI();
        currentIp = IPUtils.getIpAddr(request);
    }

    /**
     * 覆盖getParameter方法,将参数名和参数值都做xss过滤。
     * 如果需要获得原始的值,则通过super.getParameterValues(name)来获取
     * getParameterNames,getParameterValues和getParameterMap也可能需要覆盖
     */
    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    @Override
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = cleanXSS(values[i]);
        }
        return encodedValues;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        Map<String, String[]> values = super.getParameterMap();
        if (values == null) {
            return null;
        }
        Map<String, String[]> result = new HashMap<>();
        for (String key : values.keySet()) {
            String encodedKey = cleanXSS(key);
            int count = values.get(key).length;
            String[] encodedValues = new String[count];
            for (int i = 0; i < count; i++) {
                encodedValues[i] = cleanXSS(values.get(key)[i]);
            }
            result.put(encodedKey, encodedValues);
        }
        return result;
    }

    /**
     * 覆盖getHeader方法,将参数名和参数值都做xss过滤。
     * 如果需要获得原始的值,则通过super.getHeaders(name)来获取
     * getHeaderNames 也可能需要覆盖
     */
    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
        if (value == null) {
            return null;
        }
        return cleanXSS(value);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        String str = getRequestBody(super.getInputStream());
        try {
            Map<String, Object> map = JSON.parseObject(str, Map.class);
            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, cleanXSS(val.toString()));
                } else {
                    resultMap.put(key, val);
                }
            }
            str = JSON.toJSONString(resultMap);
        } catch (Exception e) {
            //e.printStackTrace();
        }
        final ByteArrayInputStream bais = new ByteArrayInputStream(str.getBytes());
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
            }
        };
    }

    private String getRequestBody(InputStream stream) {
        String line = "";
        StringBuilder body = new StringBuilder();
        int counter = 0;
        // 读取POST提交的数据内容
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream, Charset.forName("UTF-8")));
        try {
            while ((line = reader.readLine()) != null) {
                body.append(line);
                counter++;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return body.toString();
    }

    private String cleanXSS(String valueP) {
        // You'll need to remove the spaces from the html entities below
        String value = valueP.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        value = value.replaceAll("<", "& lt;").replaceAll(">", "& gt;");
//        value = value.replaceAll("\\(", "& #40;").replaceAll("\\)", "& #41;");
//        value = value.replaceAll("'", "& #39;");
        value = value.replaceAll("eval\\((.*)\\)", "");
        value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\"");
        value = value.replaceAll("script", "");
        value = cleanSqlKeyWords(value);
        return value;
    }

    private String cleanSqlKeyWords(String value) {
        String paramValue = value;
        for (String keyword : notAllowedKeyWords) {
            if (paramValue.length() >= keyword.length()
//                    && (paramValue.contains(" "+keyword)||paramValue.contains(keyword+" ")||paramValue.contains(" "+keyword+" "))) {
                    && (paramValue.trim().contains(keyword))) {
                paramValue = StringUtils.replace(paramValue, keyword, replacedString);
                log.error("IP【" + this.currentIp + "】" + this.currentUrl + "已被过滤,因为参数中包含不允许sql的关键词(" + keyword
                        + ")" + ";参数:" + value + ";过滤后的参数:" + paramValue);
            }
        }
        return paramValue;
    }
}

2.接下来的事情很简单写个过滤器


```java
package com.jgtl.smurfs.common.filter;

import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 拦截防止xss注入
 * 通过Jsoup过滤请求参数内的特定字符
 *
 * @author yangwk
 */
public class XssFilter implements Filter {
    private static final Logger logger = LogManager.getLogger();

    /**
     * 是否过滤富文本内容
     */
    private static boolean IS_INCLUDE_RICH_TEXT = false;

    public List<String> excludes = new ArrayList<>();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("xss filter is open");
        }

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeURL(req, resp)) {
            filterChain.doFilter(request, response);
            return;
        }
//        filterChain.doFilter(request, response);

        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
        filterChain.doFilter(xssRequest, response);
    }

    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {

        if (excludes == null || excludes.isEmpty()) {
            return false;
        }

        String url = request.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }

        return false;
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if (logger.isDebugEnabled()) {
            logger.debug("xss filter init ====================");
        }
        String isIncludeRichText = filterConfig.getInitParameter("isIncludeRichText");
        if (StringUtils.isNotBlank(isIncludeRichText)) {
            IS_INCLUDE_RICH_TEXT = BooleanUtils.toBoolean(isIncludeRichText);
        }

        String temp = filterConfig.getInitParameter("excludes");
        if (temp != null) {
            String[] url = temp.split(",");
            for (int i = 0; url != null && i < url.length; i++) {
                excludes.add(url[i]);
            }
        }
    }

    @Override
    public void destroy() {
    }
    }

3注入Bean


```java
package com.jgtl.smurfs.config;

import com.google.common.collect.Maps;
import com.jgtl.smurfs.common.filter.XssFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

@Configuration
public class XssConfig {
    
    

    /**
     * xss过滤拦截器
     */
    @Bean
    public FilterRegistrationBean xssFilterRegistrationBean() {
    
    
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new XssFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.setEnabled(true);
        filterRegistrationBean.addUrlPatterns("/*");
        Map<String, String> initParameters = Maps.newHashMap();
        initParameters.put("excludes", "/favicon.ico,/img/*,/js/*,/css/*");
        initParameters.put("isIncludeRichText", "true");
        filterRegistrationBean.setInitParameters(initParameters);
        return filterRegistrationBean;
    }
}

4.补工具类

package com.jgtl.smurfs.common.util;

import cn.hutool.core.util.StrUtil;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * <br/>=================================<br/>
 * 功能描述:xss攻击字典数据工具类
 
 * <br/>=================================<br/>
 */
public class XssDictUtils {
    
    

    // 防止xss攻击过滤字典map
    private static final Map<String, String> xssDicpMap = new HashMap<String, String>();

    /**
     *<br/>=================================<br/>
     *方法描述:防止xss攻击正则表达式字典收集
    
     *<br/>=================================<br/>
     *修改功能:添加对于null、NULL、0x0d、0x0a的替换规则
    
     *<br/>=================================<br/>
     *修改功能:null、NULL替换为"",打开<、>、&替换规则
     
     *<br/>=================================<br/>
     *修改功能:屏蔽&替换规则
     
     *<br/>=================================<br/>
     *修改功能:新增关键字过滤  load、create、union、rename
   
     *<br/>=================================<br/>
     *修改功能:新增关键字过滤  eval、and、or
   
     *<br/>=================================<br/>
     *修改功能:去掉关键字过滤  &
     *<br/>=================================<br/>
     */
    static {
    
    
        xssDicpMap.put("[s|S][c|C][r|R][i|I][p|P][t|T]", "");
        xssDicpMap.put("[\\\"\\\'][\\s]*[j|J][a|A][v|V][a|A][s|S][c|C][r|R][i|I][p|P][t|T]:(.*)[\\\"\\\']", "");
        xssDicpMap.put("[e|E][v|V][a|A][l|L]\\((.*)\\)", "");
        xssDicpMap.put("[v|V][b|B][s|S][c|C][r|R][i|I][p|P][t|T]", "");
        xssDicpMap.put("[s|S][o|O][u|U][r|R][c|C][e|E]", "");
//        xssDicpMap.put("<(\"[^\"]*\"|\'[^\']*\'|[^\'\">])*>", "");
        xssDicpMap.put("[e|E][x|X][p|P][r|R][e|E][s|S][s|S][i|I][o|O][n|N]", "");
//        xssDicpMap.put("[w|W][i|I][n|N][d|D][o|O][w|W]", "");
        xssDicpMap.put("[l|L][o|O][c|C][a|A][t|T][i|I][o|O][n|N]", "");
        xssDicpMap.put("[d|D][o|O][c|C][u|U][m|M][e|E][n|N][t|T]", "");
        xssDicpMap.put("[c|C][o|O][o|O][k|K][i|I][e|E]", "");
        xssDicpMap.put("[a|A][l|L][e|E][r|R][t|T]", "");
        xssDicpMap.put("[o|O][p|P][e|E][n|N]", "");
        xssDicpMap.put("[s|S][h|H][o|O][w|W][d|D][i|I][a|A][l|L][o|O][g|G]", "");
        xssDicpMap.put("[s|S][h|H][o|O][w|W][m|M][o|O][d|D][a|A][l|L][d|D][i|I][a|A][l|L][o|O][g|G]", "");
        xssDicpMap.put("[s|S][h|H][o|O][w|W][m|M][o|O][d|D][e|E][l|L][e|E][s|S][s|S][d|D][i|I][a|A][l|L][o|O][g|G]", "");
//        xssDicpMap.put("(window\\.location|window\\.|\\.location|document\\.cookie|document\\.|alert\\(.*?\\)|window\\.open\\()*", "");
        xssDicpMap.put("<+\\s*\\w*\\s*(oncontrolselect|oncopy|oncut|ondataavailable|ondatasetchanged|ondatasetcomplete|ondblclick|ondeactivate|ondrag|ondragend|ondragenter|"
                + "ondragleave|ondragover|ondragstart|ondrop|οnerrοr=|onerroupdate|onfilterchange|onfinish|onfocus|onfocusin|onfocusout|onhelp|onkeydown|onkeypress|"
                + "onkeyup|onlayoutcomplete|onload|onlosecapture|onmousedown|onmouseenter|onmouseleave|onmousemove|onmousout|onmouseover|onmouseup|onmousewheel|onmove|"
                + "onmoveend|onmovestart|onabort|onactivate|onafterprint|onafterupdate|onbefore|onbeforeactivate|onbeforecopy|onbeforecut|onbeforedeactivate|onbeforeeditocus|"
                + "onbeforepaste|onbeforeprint|onbeforeunload|onbeforeupdate|onblur|onbounce|oncellchange|onchange|onclick|oncontextmenu|onpaste|onpropertychange|"
                + "onreadystatechange|onreset|onresize|onresizend|onresizestart|onrowenter|onrowexit|onrowsdelete|onrowsinserted|onscroll|onselect|onselectionchange|"
                + "onselectstart|onstart|onstop|onsubmit|onunload)+\\s*=+", "");
        xssDicpMap.put("<", "&lt;");
        xssDicpMap.put(">", "&gt;");
//        xssDicpMap.put("&", "&amp;");
        xssDicpMap.put("[t|T][a|A][b|B][l|L][e|E]", "");
        xssDicpMap.put("[d|D][a|A][t|T][a|A][b|B][a|A][s|S][e|E]", "");
        xssDicpMap.put("[i|I][n|N][s|S][e|E][r|R][t|T]", "");
        xssDicpMap.put("[u|U][p|P][d|D][a|A][t|T][e|E]", "");
        xssDicpMap.put("[d|D][e|E][l|L][e|E][t|T][e|E]", "");
        xssDicpMap.put("[t|T][r|R][u|U][n|N][c|C][a|A][t|T][e|E]", "");
        xssDicpMap.put("[s|S][e|E][l|L][e|E][c|C][t|T]", "");
        xssDicpMap.put("[a|A][l|L][t|T][e|E][r|R]", "");
        xssDicpMap.put("[n|N][u|U][l|L][l|L]", "\"\"");
        xssDicpMap.put("[d|D][e|E][s|S][c|C]", "\"\"");
        xssDicpMap.put("0[x|X]0[d|D]", "");
        xssDicpMap.put("[l|L][o|O][a|A][d|D]", "\"\"");
        xssDicpMap.put("[r|R][e|E][n|N][a|A][m|M][e|E]", "");
        //xssDicpMap.put("[c|C][r|R][e|E][a|A][t|T][e|E]", "");
        xssDicpMap.put("[u|U][n|N][i|I][o|O][n|N]", "");
        xssDicpMap.put("[e|E][x|X][i|I][s|S][t|T][s|S]", "");
        xssDicpMap.put("[e|E][v|V][a|A][l|L]", "");
        xssDicpMap.put("[a|A][n|N][d|D]", "");
        xssDicpMap.put("[o|O][r|R] ", "");
    }


   
    public static String xssValid(String value) {
    
    
        if (StrUtil.isEmpty(value)) {
    
    
            return value;
        }
        for (String key : xssDicpMap.keySet()) {
    
    
            Pattern p = Pattern.compile(key);
            Matcher m = p.matcher(value);
            value = m.replaceAll(xssDicpMap.get(key));
        }
        return value;
    }

 
    public static void main(String[] args) {
    
    
        String temp = "asset_m_windows";
        System.out.println(XssDictUtils.xssValid(temp));
    }

}

猜你喜欢

转载自blog.csdn.net/weixin_45818787/article/details/108371073
今日推荐