• AntiSamy防跨站脚本攻击(XSS)快速入门


    AntiSamy防跨站脚本攻击(XSS)快速入门

    1、XSS介绍

    XSS:跨站脚本攻击(Cross Site Scripting),为不和 CSS混淆,故将跨站脚本攻击缩写为XSS。XSS是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。有点类似于sql注入

    XSS攻击原理:

    HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,之间的字符是页面的标题等等。当动态页面中插入的内容含有这些特殊字符时,用户浏览器会将其误认为是插入了HTML标签,当这些HTML标签引入了一段JavaScript脚本时,这些脚本程序就将会在用户浏览器中执行。所以,当这些特殊字符不能被动态页面检查或检查出现失误时,就将会产生XSS漏洞。

    模拟简单xss注入:

    比如添加用户信息的form表单,可以输入用户名和密码以及手机号等表单选项,我们在输入表单数据的时候可以直接录入如下JavaScript代码

    当上面的代码段被保存到数据库的时候,然后被展示出来HTML文本结构时会将代码按照编写的条件进行执行(比如循环1000编弹窗信息),从而影响用户的体验和安全性

    2、AntiSamy介绍

    AntiSamy是OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。

    AntiSamy的maven坐标:

    <dependency>
      <groupId>org.owasp.antisamygroupId>
      <artifactId>antisamyartifactId>
      <version>1.5.7version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、 AntiSamy入门案例

    3.1、第一步:创建maven工程antiSamy_demo并配置pom.xml文件
    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
        <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.2.2.RELEASEversion>
            <relativePath/>
        parent>
        <groupId>org.examplegroupId>
        <artifactId>antiSamy_demoartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
            <dependency>
                <groupId>org.owasp.antisamygroupId>
                <artifactId>antisamyartifactId>
                <version>1.5.7version>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
        dependencies>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    3.2、第二步:创建application.yml

    可创建也可不创建

    server:
      port: 9000
    
    • 1
    • 2
    3.3、第三步:创建策略文件/resources/antisamy-test.xml,文件内容可以从antisamy的jar包中获取

    注:AntiSamy对“恶意代码”的过滤依赖于策略文件。策略文件规定了AntiSamy对各个标签、属性的处理方法,策略文件定义的严格与否,决定了AntiSamy对XSS漏洞的防御效果。在AntiSamy的jar包中,包含了几个常用的策略文件
    在这里插入图片描述

    将AntiSamy的jar包中的antisamy-ebay.xml文件复制到项目的resoruces资源文件中,并修改名称为antisamy-test.xml

    3.4、第四步:创建User实体类
    package com.zcl.entity;
    
    import lombok.Data;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:用户实体类
     *
     * @author zhong
     * @date 2022-08-29 17:54
     */
    @Data
    public class User {
        private int id;
        private String name;
        private int age;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    3.5、创建UserController控制器
    package com.zcl.controller;
    
    import com.zcl.entity.User;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:用户控制器
     *
     * @author zhong
     * @date 2022-08-29 17:55
     */
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @RequestMapping("/save")
        public String save(User user){
            System.out.println("UserController save.... " + user);
            return user.getName();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    3.6、第六步:创建/resources/static/index.html页面

    该表单提交的内容会请求到上面编写的保存控制器

    DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Titletitle>
    head>
    <body>
    <form method="post" action="/user/save">
      id:<input type="text" name="id"><br>
      name:<input type="text" name="name"><br>
      age:<input type="text" name="age"><br>
      <input type="submit" value="submit">
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    3.7、第七步:创建启动类
    package com.zcl;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:项目启动类
     *
     * @author zhong
     * @date 2022-08-29 17:57
     */
    @SpringBootApplication
    public class AntiSamyApp {
        public static void main(String[] args) {
            SpringApplication.run(AntiSamyApp.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    此时我们可以启动项目进行访问,但是还没有进行参数的过滤,所以如果我们输入任意参数都可以正常传递到Controller中,这在实际项目中是非常不安全的。为了对我们输入的数据进行过滤清理,需要通过过滤器来实现。

    3.8、启动项目测试XSS注入

    谷歌的浏览器对应xss攻击做了一定的识别,是会被拦截在控制台打印错误信息的,可以使用其他浏览器进行测试
    在这里插入图片描述

    启动项目后访问index.html页面,提交一个JavaScript标签的内容到后端,后端又会返回给前端,前端就会执行该JavaScript代码弹出123

    在这里插入图片描述

    3.8、第八步:创建过滤器,用于过滤所有提交到服务器的请求参数
    package com.zcl.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:过滤所有提交到服务器的请求参数
     *
     * @author zhong
     * @date 2022-08-29 18:12
     */
    public class XssFilter implements Filter {
        /**
         * 拦截请求
         *
         * @param servletRequest  请求对象
         * @param servletResponse 拦截对象
         * @param filterChain     过滤链
         * @throws IOException      异常
         * @throws ServletException 小服务程序异常
         */
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                             FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            //传入重写后的Request【包装】
            filterChain.doFilter(new XssRequestWrapper(request), servletResponse);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    注意:通过上面的过滤器可以发现我们并没有在过滤器中直接进行请求参数的过滤清理,而是直接放行了,那么我们还怎么进行请求参数的过滤清理呢?其实过滤清理的工作是在另外一个类XssRequestWrapper中进行的,当上面的过滤器放行时需要调用filterChain.doFilter()方法,此方法需要传入请求Request对象,此时我们可以将当前的request对象进行包装,而XssRequestWrapper就是Request对象的包装类,在过滤器放行时会自动调用包装类的getParameterValues方法,我们可以在包装类的getParameterValues方法中进行统一的请求参数过滤清理。

    3.9、创建XssRequestWrapper类
    package com.zcl.filter;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:过滤请求参数包装返回
     *
     * @author zhong
     * @date 2022-08-29 18:19
     */
    public class XssRequestWrapper extends HttpServletRequestWrapper {
        public XssRequestWrapper(HttpServletRequest request) {
            super(request);
        }
    
        /**
         * 当过滤器被放行的时候调用【所有的表单请求都会进过一遍】
         * @param name
         * @return
         */
        @Override
        public String[] getParameterValues(String name) {
        	System.out.println(name + " " +super.getParameterValues(name));
            return super.getParameterValues(name);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    4.0、第十步:为了使上面定义的过滤器生效,需要创建配置类,用于初始化过滤器对象

    配置拦截所有的请求过滤器,过滤器就会调用doFilter,然后doFilter直接放行并把对应的请求参数放行,在上面的代码中放行请求参数时进行了二次包装【new XssRequestWrapper(request)】,包装类的getParameterValues()方法会根据页面提交的几个参数就会执行几次 ,所以过滤请求参数的代码就可以在该方法中进行处理

    package com.zcl.config;
    
    import com.zcl.filter.XssFilter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:初始化过滤器对象
     *
     * @author zhong
     * @date 2022-08-29 18:25
     */
    @Configuration
    public class AntiSamyConfiguration {
        /**
         * 配置跨站攻击过滤器
         */
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean filterRegistration =
                    new FilterRegistrationBean(new XssFilter());
            // 拦截所有
            filterRegistration.addUrlPatterns("/*");
            // 执行的先后顺序
            filterRegistration.setOrder(1);
            return filterRegistration;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    4.1、断点调试代码

    com.zcl.filter.XssRequestWrapper类上的【getParameterValues】方法上进行断点调试,前端页面请求几个参数就会进行几次这个方法

    在这里插入图片描述

    当前端进行表单提交的时候,后面就会拦截到
    在这里插入图片描述

    断点拦截

    打印出来的是一个数组空间,可以使用IDEA的计算方式直接到具体的值,看下面的操作

    在这里插入图片描述

    IDEA计算数组的值

    在这里插入图片描述

    计算结果

    在这里插入图片描述

    4.2、对XssRequestWrapper包装类进行改进

    注意:当前我们在进行请求参数过滤时只是在包装类的getParameterValues方法中进行了处理,真实项目中可能用户提交的数据在请求头中,也可能用户提交的是json数据,所以如果考虑所有情况,我们可以在包装类中的多个方法中都进行清理处理即可,如下:

    package com.zcl.filter;
    
    import org.owasp.validator.html.*;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.util.Map;
    
    /**
     * 项目名称:antiSamy_demo
     * 描述:过滤请求参数包装返回
     *
     * @author zhong
     * @date 2022-08-29 18:19
     */
    public class XssRequestWrapper extends HttpServletRequestWrapper {
    
        /**
         * 策略文件 需要将要使用的策略文件放到项目资源文件路径下
         * */
        private static String antiSamyPath = XssRequestWrapper.class.getClassLoader().getResource( "antisamy-test.xml").getFile();
    
        public static Policy policy = null;
        static {
            // 指定策略文件
            try {
                policy = Policy.getInstance(antiSamyPath);
            } catch (PolicyException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * AntiSamy框架过滤数据
         * @param text
         * @return
         */
        public String cleanXss(String text){
            AntiSamy antiSamy = new AntiSamy();
            try {
                // 过滤之后的结果【对文本进行扫描和指定策略】
                CleanResults cleanResults = antiSamy.scan(text, policy);
                // 间过滤之后的数据重新赋值给text对象
                text = cleanResults.getCleanHTML();
            } catch (ScanException e) {
                e.printStackTrace();
            } catch (PolicyException e) {
                e.printStackTrace();
            }
            return text;
        }
    
        public XssRequestWrapper(HttpServletRequest request) {
            super(request);
        }
    
        /**
         * 当过滤器被放行的时候调用【所有的表单请求都会进过一遍】
         * @param name
         * @return
         */
        @Override
        public String[] getParameterValues(String name) {
            String[] parameterValues = super.getParameterValues(name);
            // 判断数组不等于空
            if(parameterValues == null){
                return null;
            }
            // 获取到数组的长度
            int length = parameterValues.length;
            // 创建新的数组进行返回
            String[] newArray = new String[length];
            // 遍历数组调用AntiSamy框架过滤数据
            for (int i = 0; i < parameterValues.length; i++) {
                String parameterValue = parameterValues[i];
                // 将过滤非法字符的数据返回
                parameterValue = cleanXss(parameterValue);
                // 添加到新的数组中
                newArray[i] = parameterValue;
            }
            // 最终返回新的数组
            return newArray;
        }
    
        @Override
        public String getParameter(String paramString) {
            String str = super.getParameter(paramString);
            if (str == null) {
                return null;
            }
            return cleanXss(str);
        }
    
    
        @Override
        public String getHeader(String paramString) {
            String str = super.getHeader(paramString);
            if (str == null) {
                return null;
            }
            return cleanXss(str);
        }
    
        @Override
        public Map<String, String[]> getParameterMap() {
            Map<String, String[]> requestMap = super.getParameterMap();
            for (Map.Entry<String, String[]> me : requestMap.entrySet()) {
                String[] values = me.getValue();
                for (int i = 0; i < values.length; i++) {
                    values[i] = cleanXss(values[i]);
                }
            }
            return requestMap;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    4.3、启动项目进行测试

    注意:该项目工程目录一定不能出现中文目录,否则提交数据的时候可能会出现访问antisamy-test.xml路径错误
    1、提交带有javaScript的表单数据
    带有JavaScript的数据会直接过滤掉

    UserController save.... User(id=12, name=, age=18)
    
    • 1

    2、提交正常的表单数据
    提交正常的文本不会被拦截

    UserController save.... User(id=12, name=我是合法字段, age=18)
    
    • 1

    使用策略文件指定的会移除的标签如下
    初略不齐全

    <tag name="script" action="remove"/>
    <tag name="noscript" action="validate"/> 
    
    <tag name="iframe" action="remove"/>
    <tag name="frameset" action="remove"/>
    <tag name="frame" action="remove"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 相关阅读:
    你晓得小程序的身世么?
    KMP字符串搜索算法及next数组计算
    队列题目:用栈实现队列
    springboot中使用mybatis的类型转换器
    leetcode 215.数组中第k大的元素
    Android 13 现已正式发布,看看有哪些更新!
    docker-compose搭建的mysql,如何定时备份数据
    微服务平滑迁移上云最佳实践
    Julia累加和累乘
    EOS账户映射及key、account、wallet
  • 原文地址:https://blog.csdn.net/baidu_39378193/article/details/126587090