Web系统安全问题整理(XSS攻击)


在不久之前曾介绍过系统中间件版本号暴露和重放安全问题,这次主要讲一下XSS攻击防护措施。

1. XSS介绍

XSS(Cross Site Scripting), 中文名为跨站脚本, 是发生在目标用户的浏览器层面上的,当渲染DOM树的过程成发生了不在预期内执行的JS代码时,就发生了XSS攻击。跨站脚本的重点不在“跨站”上,而在于“跨站”上。大多数XSS攻击的主要方式是嵌入一段远程或者第三方域上的JS代码。实际上是在目标网站的作用域下执行了这段js代码。

2. XSS分类

2.1 反射型XSS

反射型XSS主要表现在浏览器的展示层面上,并没有将脚本数据持久化到数据库中,攻击者通过拦截服务器返回给页面的数据,利用工具在服务器返回的数据中加入攻击脚本,进而在页面上进行展示。严重的还可以窃取用户密码等信息。

2.1 存储型XSS

顾名思义,它和反射型XSS的差别仅在于,提交的代码会存储在服务器端(数据库,内存,文件系统等),下次请求目标页面时不用再提交XSS代码。最典型的例子是留言板XSS,用户提交一条包含XSS代码的留言存储到数据库,目标用户查看留言板时,那些留言的内容会从数据库查询出来并显示,浏览器发现有XSS代码,就当做正常的HTML与JS解析执行,于是触发了XSS攻击。

3. 安全测试工具

如果测试系统是否含有此漏洞,可以使用抓包工具进行测试,在这里我推荐一个工具,也是大多数安全测试人员使用的工具,工具名称为Burpsuite。想要使用此工具还要需要配置一下浏览器。这里主要介绍Burpsuite+Firefox浏览器。
配置方法:http://www.cnblogs.com/slpawn/p/7235105.html
Burpsuite和Firefox证书下载路径:https://download.csdn.net/download/li521wang/10938753
具体的使用方法就不这里详细讲了。

4. 代码过滤XSS攻击

利用Filter进行XSS过滤。首先先增加几个配置类管理XSS过滤参数以及过滤的内容。

4.1. XSSSecurityManager 安全过滤配置管理类

/**
 * @Author: LX 17839193044@162.com
 * @Description: 安全过滤配置管理类,由XSSSecurityManger修改。(admin迁移至此)
 * @Date: 2019/1/24 14:55
 * @Version: V1.0
 */
@Slf4j
public class XSSSecurityManager {

    /**
     * REGEX:校验正则表达式
     */
    public static String REGEX;

    public static String[] REGEXS = new String[15];

    /**
     * 特殊字符匹配
     */
    public static Pattern XSS_PATTERN;

    /**
     * 设置私有构造方法
     */
    private XSSSecurityManager() {
    }

    /**
     * @Author: LX 17839193044@162.com
     * @Description: 初始化过滤匹配的字符
     * @Date: 2019/1/24 15:20
     * @Version: V1.0
     */
    public static void init() {
        log.info("XSSSecurityManager.initConfig(String path) begin");

        //  匹配含有字符: alert
        REGEXS[0] = ".*[A|a][L|l][E|e][R|r][T|t]\\s*\\(.*\\).*";
        //  匹配含有字符: window.location =
        REGEXS[1] = ".*[W|w][I|i][N|n][D|d][O|o][W|w]\\.[L|l][O|o][C|c][A|a][T|t][I|i][O|o][N|n]\\s*=.*";
        //  匹配含有字符:style = x:ex pression ( ) 
        REGEXS[2] = ".*[S|s][T|t][Y|y][L|l][E|e]\\s*=.*[X|x]:[E|e][X|x].*[P|p][R|r][E|e][S|s]{1,2}[I|i][O|o][N|n]\\s*\\(.*\\).*";
        //  匹配含有字符: document.cookie 
        REGEXS[3] = ".*[D|d][O|o][C|c][U|u][M|m][E|e][N|n][T|t]\\.[C|c][O|o]{2}[K|k][I|i][E|e].*";
        //  匹配含有字符: eval( ) 
        REGEXS[4] = ".*[E|e][V|v][A|a][L|l]\\s*\\(.*\\).*";
        //  匹配含有字符: unescape() 
        REGEXS[5] = ".*[U|u][N|n][E|e][S|s][C|c][A|a][P|p][E|e]\\s*\\(.*\\).*";
        //  匹配含有字符: execscript( ) 
        REGEXS[6] = ".*[E|e][X|x][E|e][C|c][S|s][C|c][R|r][I|i][P|p][T|t]\\s*\\(.*\\).*";
        //  匹配含有字符: msgbox( ) 
        REGEXS[7] = ".*[M|m][S|s][G|g][B|b][O|o][X|x]\\s*\\(.*\\).*";
        //  匹配含有字符: confirm( ) 
        REGEXS[8] = ".*[C|c][O|o][N|n][F|f][I|i][R|r][M|m]\\s*\\(.*\\).*";
        //  匹配含有字符: prompt( ) 
        REGEXS[9] = ".*[P|p][R|r][O|o][M|m][P|p][T|t]\\s*\\(.*\\).*";
        //  匹配含有字符: <script> </script> 
        REGEXS[10] = ".*<[S|s][C|c][R|r][I|i][P|p][T|t]>.*</[S|s][C|c][R|r][I|i][P|p][T|t]>.*";
        //  匹配含有字符: 含有一个符号: " 
        REGEXS[11] = "[.&[^\"]]*\"[.&[^\"]]*";
        //  匹配含有字符: 含有一个符号: ' 
        REGEXS[12] = "[.&[^']]*'[.&[^']]*";
        //  匹配含有字符: 含有回车换行 和 <script> </script> 
        REGEXS[13] = ".&[^a]]|[|a|\n|\r\n|\r|\u0085|\u2028|\u2029]]*<[S|s][C|c][R|r][I|i][P|p][T|t]>.*</[S|s][C|c][R|r][I|i][P|p][T|t]>[[.&[^a]]|[|a|\n|\r\n|\r|\u0085|\u2028|\u2029]]*";
        //  匹配特殊sql字符 
        REGEXS[14] = "(?:')|(?:--)|(/\\*(?:.|[\\n\\r])*?\\*/)|" + "(.*\\b(select|update|and|or|delete|insert|trancate|char|into|substr|ascii|declare|exec|count|master|into|drop|execute)\\b.*)";

        StringBuffer sb = new StringBuffer("^");
        for (String tmp : REGEXS) {
            tmp = tmp.replaceAll("\\\\\\\\", "\\\\");
            sb.append(tmp);
            sb.append("|");
        }

        if (sb.charAt(sb.length() - 1) == '|') {
            REGEX = sb.substring(0, sb.length() - 1) + "$";
            log.info("安全匹配规则" + REGEX);
        } else {
            log.error("安全过滤配置文件加载失败:正则表达式异常 " + sb.toString());
        }
        // 生成匹配器
        XSS_PATTERN = Pattern.compile(REGEX);

        log.info("XSSSecurityManager.initConfig(String path) end");
    }


    /**
     * 匹配字符是否含特殊字符
     *
     * @param text
     * @return
     */
    public static boolean matches(String text) {
        if (StringUtils.isBlank(text)) {
            return false;
        }
        return XSS_PATTERN.matcher(text).matches();
    }
}

4.2. XSSSecurityConfig 开关配置类

/**
 * @Author: LX 17839193044@162.com
 * @Description: XSS配置类
 * @Date: 2019/1/24 14:54
 * @Version: V1.0
 */
public class XSSSecurityConfig {

    private XSSSecurityConfig() {
    }

    /**
     * CHECK_HEADER:是否开启header校验
     */
    public static boolean IS_CHECK_HEADER = false;

    /**
     * CHECK_PARAMETER:是否开启parameter校验
     */
    public static boolean IS_CHECK_PARAMETER = true;

    /**
     * IS_LOG:是否记录日志
     */
    public static boolean IS_LOG = true;

    /**
     * IS_LOG:是否中断操作
     */
    public static boolean IS_CHAIN = false;

    /**
     * REPLACE:是否开启替换
     */
    public static boolean REPLACE = true;


    /**
     * FILTER_ERROR_PAGE:过滤后错误页面
     */
    public static String FILTER_ERROR_PAGE2 = "/error";

    /**
     * IS_FILTER_REFERER:是否开启防盗链
     */
    public static boolean IS_FILTER_REFERER = false;

}

4.3. XssHttpServletRequestWrapper 自定义XSS过滤wrapper

/**
 * @Author: LX 17839193044@162.com
 * @Description: 自定义XSS过滤wrapper
 * @Date: 2019/1/24 14:50
 * @Version: V1.0
 */
@Slf4j
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

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

    @Override
    public String getHeader(String name) {
        String value = super.getHeader(name);
//        // 若开启特殊字符替换,对特殊字符进行替换
//        if (XSSSecurityConfig.REPLACE && StringUtils.isNotBlank(value)) {
//            return stringFilter(value);
//        }
        return value;
    }

    @Override
    public String getQueryString() {
        String queryString = super.getQueryString();
        // 若开启特殊字符替换,对特殊字符进行替换
        if (XSSSecurityConfig.REPLACE && StringUtils.isNotBlank(queryString)) {
            return stringFilter(queryString);
        }
        return StringEscapeUtils.escapeHtml4(super.getQueryString());
    }

    @Override
    public String getParameter(String name) {
        String value = super.getParameter(name);
        // 若开启特殊字符替换,对特殊字符进行替换
        if (XSSSecurityConfig.REPLACE && StringUtils.isNotBlank(value)) {
            return stringFilter(value);
        }
        return value;
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = super.getParameterValues(name);
        if (values != null) {
            int length = values.length;
            String[] escapseValues = new String[length];
            for (int i = 0; i < length; i++) {
                escapseValues[i] = StringEscapeUtils.escapeHtml4(values[i]);
            }
            return escapseValues;
        }
        return super.getParameterValues(name);
    }

    /**
     * 没有违规的数据,就返回false;
     * 若存在违规数据,根据配置信息判断是否跳转到错误页面
     *
     * @return
     * @throws IOException
     * @throws ServletException
     */
    public boolean validateParameter() {
        // 开始header校验,对header信息进行校验
        if (XSSSecurityConfig.IS_CHECK_HEADER) {
            if (this.checkHeader()) {
                return true;
            }
        }
        // 开始parameter校验,对parameter信息进行校验
        if (XSSSecurityConfig.IS_CHECK_PARAMETER) {
            if (this.checkParameter()) {
                return true;
            }
        }
        return false;
    }


    /**
     * 没有违规的数据,就返回false;
     *
     * @return
     */
    private boolean checkHeader() {
        Enumeration<String> headerParams = this.getHeaderNames();
        while (headerParams.hasMoreElements()) {
            String headerName = headerParams.nextElement();
            String headerValue = this.getHeader(headerName);
            if (XSSSecurityManager.matches(headerValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 没有违规的数据,就返回false;
     *
     * @return
     */
    private boolean checkParameter() {
        Map<String, String[]> submitParams = this.getParameterMap();
        Set<String> submitNames = submitParams.keySet();
        for (String submitName : submitNames) {
            String[] submitValues = submitParams.get(submitName);
            for (String submitValue : submitValues) {
                try {
                    submitValue = StringEscapeUtils.unescapeHtml4(submitValue);
                    log.debug(submitName + ":" + submitValue + "----" + XSSSecurityManager.matches(submitValue));
                    if (XSSSecurityManager.matches(submitValue)) {
                        return true;
                    }
                } catch (Exception e) {
                    log.info("地址解码异常:" + submitValue);
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 过滤字符串里的的特殊字符
     *
     * @param str 要过滤的字符串
     * @return 过滤后的字符串
     */
    public static String stringFilter(String str) {
        String temp = StringUtils.replace(str, "%27", "");
        temp = StringUtils.replace(temp, "*", "");
        temp = StringUtils.replace(temp, "\"", "&quot;");
        temp = StringUtils.replace(temp, "'", "");
        temp = StringUtils.replace(temp, "\\\"", "");
        temp = StringUtils.replace(temp, ";", "");
        temp = StringUtils.replace(temp, "<", "&lt;");
        temp = StringUtils.replace(temp, ">", "&gt;");
        temp = StringUtils.replace(temp, "(", "");
        temp = StringUtils.replace(temp, ")", "");
        temp = StringUtils.replace(temp, "{", "");
        temp = StringUtils.replace(temp, "}", "");
        return temp.trim();
    }

}

4.4. XssFilter 过滤器,拦截所有请求,对Header、Parameter进行过滤

/**
 * @Author: LX 17839193044@162.com
 * @Description: XSS过滤器 拦截所有请求,对Header、Parameter进行过滤
 * @Date: 2019/1/24 14:51
 * @Version: V1.0
 */
@Slf4j
public class XssFilter implements Filter {

    /**
     * @Author: LX 17839193044@162.com
     * @Description: 项目启动时初始化
     * @Date: 2019/1/24 16:11
     * @Version: V1.0
     */
    @Override
    public void init(FilterConfig filterConfig) {
        //初始化XSS过滤词库
        XSSSecurityManager.init();
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // LX 17839193044@162.com 此处增加CSRF过滤  下编讲

        // http信息封装类
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(req);
        String pageLink = xssRequest.getServletPath();
        if (StringUtils.isNotBlank(pageLink)) {

            // 对request信息进行封装并进行校验工作,若校验失败(含非法字符),
            // 根据配置信息进行日志记录和请求中断处理
            if (!pageLink.equals(XSSSecurityConfig.FILTER_ERROR_PAGE2)
                    && xssRequest.validateParameter()) {
                //判断是否记录日志
                if (XSSSecurityConfig.IS_LOG) {
                    // 记录攻击访问日志 可使用数据库、日志、文件等方式
                    log.error("访问IP:{},请求后缀:{},查询参数:{}", request.getRemoteAddr(),
                            pageLink, xssRequest.getQueryString());
                }
                if (XSSSecurityConfig.IS_CHAIN) {
                    //进行页面跳转
                    request.getRequestDispatcher(XSSSecurityConfig.FILTER_ERROR_PAGE2).forward(request, resp);
                    return;
                }
                chain.doFilter(xssRequest, resp);
            } else {
                chain.doFilter(xssRequest, resp);
            }
        } else {
            chain.doFilter(xssRequest, resp);
        }
    }

    /**
     * @Author: LX 17839193044@162.com
     * @Description: 生命周期结束时调用
     * @Date: 2019/1/24 16:11
     * @Version: V1.0
     */
    @Override
    public void destroy() {
    }
}

4.5. web.xml 最重要的一步,不解释。

  <!-- XSS过滤器 -->
    <filter>
        <filter-name>XssFilter</filter-name>
        <filter-class>com.***.XssFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>XssFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

到此处将修改后的代码部署到服务器。利用Burpsuite再次进行安全测试,XSS攻击已经被我们过滤了。如果大家还有其他比较好的XSS防护对策,可以分享一下。万分感谢!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值