防xss攻击

一、背景

介绍:XSS(Cross Site Scripting)指的是用户注入恶意的代码,浏览器和服务器没有对用户的输入进行过滤,导致用户注入的脚本嵌入到了页面中。由于浏览器无法识别这些恶意代码正常解析执行,攻击者的恶意操作被成功执行。

预防XSS攻击不仅是前端开发人员要做的事情,也是是后端开发人员要做的事情。本篇章节是针对后端开发人员怎么预防XSS攻击。

常见的XSS攻击分为三种:

  1. 反射型: 通过在请求地址上加上恶心的HTML代码。
  2. dom型: 通过一些api向网站注入一些恶心的HTML代码。
  3. 持久型: 攻击者通过把代码提交到后台数据库中;当用户下次打开的时候就会从后台接收这些恶意的代码。

防范:

  1. 反射型: 前端通过转义来进行防范以及过滤
  2. dom型:前端通过转义来进行防范以及过滤
  3. 持久型:服务端通过转义存储进行防范

对于反射型与dom型的XSS攻击,需要前端做转义。本篇博客主要讲解后端的转义处理。

二、目的

  1. 针对输入包含dom敏感的数据进行过滤
  2. 针对输入包含sql相关的敏感信息进行过滤

三、实现方式

使用Filter过滤器

使用Filter,将所有的敏感信息替换成空字符串
public class XSSPreventionFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        XSSRequestWrapper wrapper = new XSSRequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrapper, response);
    }
 
    class XSSRequestWrapper extends HttpServletRequestWrapper {
 
        private Map<String, String[]> sanitizedQueryString;
 
        XSSRequestWrapper(HttpServletRequest request) {
            super(request);
        }
 
 
        @Override
        public String getParameter(String name) {
            String parameter = null;
            String[] vals = getParameterMap().get(name);
 
            if (vals != null && vals.length > 0) {
                parameter = vals[0];
            }
 
            return parameter;
        }
 
        @Override
        public String[] getParameterValues(String name) {
            return getParameterMap().get(name);
        }
 
        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(getParameterMap().keySet());
        }
 
        @Override
        public Map<String, String[]> getParameterMap() {
            if (sanitizedQueryString == null) {
                Map<String, String[]> res = new HashMap<>();
                Map<String, String[]> originalQueryString = super.getParameterMap();
                if (originalQueryString != null) {
                    for (String key : originalQueryString.keySet()) {
                        String[] rawVals = originalQueryString.get(key);
                        String[] snzVals = new String[rawVals.length];
                        for (int i = 0; i < rawVals.length; i++) {
                            snzVals[i] = stripXSS(rawVals[i]);
                        }
                        res.put(stripXSS(key), snzVals);
                    }
                }
                sanitizedQueryString = res;
            }
            return sanitizedQueryString;
        }
 
 
        /**
         * 从字符串中删除所有潜在的恶意字符
         *
         * @param value the raw string
         * @return the sanitized string
         */
        private String stripXSS(String value) {
            String cleanValue = null;
            if (value != null) {
                cleanValue = Normalizer.normalize(value, Normalizer.Form.NFD);
 
                // 删除空字符
                cleanValue = cleanValue.replaceAll("\0", "");
 
                // 删除<script></script>标签
                Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除src='...'
                scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*'(.*?)'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除</script>标签
                scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除<script ...>标签
                scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除eval(...)表达式
                scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除expression(...)表达式
                scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除javascript:...表达式
                scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除vbscript:...表达式
                scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除onload= 表达式
                scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除 sql ' 和 ; 字符串
                scriptPattern = Pattern.compile("[';]", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
                // 删除 sql -- 字符
                scriptPattern = Pattern.compile("--", Pattern.CASE_INSENSITIVE);
                cleanValue = scriptPattern.matcher(cleanValue).replaceAll("");
 
            }
            return cleanValue;
        }
    }
 
}

配置Filter过滤器

配置xss防注入filter bean
@Configuration
public class FilterConfig {
 
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean frb = new FilterRegistrationBean();
        frb.setFilter(new XSSPreventionFilter());
        frb.setOrder(1);
        frb.addUrlPatterns("/*");
        return frb;
    }
 
 
}

以上完成,搞定。