欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-07 17:38:39
...


在不久之前曾介绍过系统中间件版本号暴露和重放安全问题,这次主要讲一下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 [email protected]
 * @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 [email protected]
     * @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 [email protected]
 * @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 [email protected]
 * @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 [email protected]
 * @Description: XSS过滤器 拦截所有请求,对Header、Parameter进行过滤
 * @Date: 2019/1/24 14:51
 * @Version: V1.0
 */
@Slf4j
public class XssFilter implements Filter {

    /**
     * @Author: LX [email protected]
     * @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 [email protected] 此处增加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 [email protected]
     * @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防护对策,可以分享一下。万分感谢!

相关标签: 安全整理