• AntiSamy:防 XSS 攻击的一种解决方案使用教程


    1. XSS 介绍

    XSS 是跨站脚本攻击(Cross Site Scripting) 的简称,为不和 CSS(Cascading Style Sheets) 混淆,故将跨站脚本攻击缩写为 XSS. XSS 是指恶意攻击者往 Web 页面里插入恶意 Script 代码,当用户浏览该页时,嵌入其中 Web 里面的 Script 代码会被执行,从而达到恶意攻击用户的目的。有点类似于 SQL 注入。当网站攻击者发现这个漏洞,并攻击成功后,攻击者可能得到包括但不限于更高的权限(如执行一些操作)、私密网页内容、会话和 cookie 等各种内容。

    XSS 攻击分为两种类型

    • 持久型:XSS 攻击代码被存储到服务器的数据库中,隐秘性很高。例如,当攻击者在评论或留言板注入 XSS 攻击代码,而帖子或博客被服务器存储下来,帖子的评论或留言板自然也就被持久化到服务器的数据库中,这里面就包含了 XSS 攻击代码。当其他用户浏览这个帖子的时候,XSS 攻击代码便开始在用户的浏览器中解析并执行。
    • 反射型:反射型 XSS 又称为非持久型 XSS,这种攻击方式具有一次性的特点。例如,攻击者将包含 XSS 代码的恶意链接发送给用户,当用户访问链接时,服务器收到用户请求并进行处理,再将包含 XSS 代码的数据返回给用户的浏览器,那么用户浏览器解析包含 XSS 代码的数据时,就会触发 XSS 漏洞。
    • DOM 型:区别于以上两种类型,DOM 型 XSS 攻击不经过服务器,它是由攻击者直接构造一个包含 XSS 攻击代码的 URL,然后让目标用户去访问这个 URL,用户的浏览器在处理这个响应的时候,DOM 型对象就会处理 XSS 代码,触发 XSS 漏洞。

    2. AntiSamy 介绍

    因此为了避免这个漏洞给网站的用户带来的危害,OWASP 组织开源了一个叫做 AntiSamy 的项目,帮助我们的网站防御 XSS 攻击。它通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy 被广泛应用于 Web 服务对存储型和反射型 XSS 的防御中。

    官方给出的关于 AntiSamy 的介绍是这样的:

    AntiSamy 是一个 API 或 库 ,可以帮助我们开发者确保客户端不会在他们提供的 HTML 中提供恶意的代码,这些 HTML 用于保存在服务器上的配置文件、注释等。关于 web 应用程序的术语“恶意代码”通常指“JavaScript”。大多数情况下,CSS 只有在调用 JavaScript 时才被认为是恶意的。然而,在许多情况下,“正常的” HTML 和 CSS 可以被恶意使用。

    3. AntiSamy 使用

    3.1 导入依赖

    AntiSamy 的 maven 坐标:

    <dependency>
      <groupId>org.owasp.antisamy</groupId>
      <artifactId>antisamy</artifactId>
      <version>1.6.2</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.2 选择策略文件

    AntiSamy 预定义了一些策略文件,这些策略文件它们代表了允许用户提供 HTML (可能还有CSS) 格式化信息的典型应用场景,我们可以根据自己的应用场景选择合适的策略文件。具体的策略文件有以下几种:

    1、antisamy-slashdot.xml

    • Slashdot 是一个技术新闻网站,它允许用户匿名回复非常有限的 HTML 标记的新闻帖子。现在,Slashdot 不仅是最酷的网站之一,它也是一个受到许多不同成功攻击的网站。
    • Slashdot 的规则相当严格:用户只能提交以下 <b>, <u>, <i>, <a>, <blockquote> 这些 HTML 标记,不能提交 CSS.
    • 因此,antisamy-slashdot.xml 文件支持类似的功能,所有直接对字体、颜色或重点进行操作的文本格式标记都是允许的,但是不允许 CSS 和 JavaScript 标记出现。

    2、antisamy-ebay.xml

    • eBay 是世界上最受欢迎的在线拍卖网站,它是一个公共站点,因此任何人都可以发布包含丰富 HTML 内容的清单。考虑到 eBay 作为一个有吸引力的目标,它受到一些复杂的 XSS 攻击并不奇怪。清单被允许包含比 Slashdot 更丰富的内容——所以它的攻击面相当大。
    • 因此,antisamy-ebay.xml 策略文件提供的策略是支持丰富的 HTML 标记,但是不支持 CSS 标记 和 JavaScript 标记。

    3、antisamy-myspace.xml

    • MySpace 是一个曾经非常受欢迎的社交网站,用户可以提交几乎所有他们想要的 HTML 和CSS ——只要不包含 JavaScript. MySpace 使用一个单词黑名单来验证用户的 HTML,这就是为什么他们会受到臭名昭著的 Samy 蠕虫的攻击。Samy 蠕虫使用碎片攻击和一个应该被列入黑名单的词(eval)——是这个项目的灵感来源。
    • 因此,antisamy-myspace.xml 策略文件提供的策略是支持非常丰富的 HTML 和 CSS 标记,但是不支持 JavaScript 标记。

    4、antisamy-anythinggoes.xml

    • 如果想允许每一个有效的 HTML 和 CSS 元素(但是不允许 JavaScript 或明显的 CSS 相关的钓鱼攻击),你可以使用这个策略文件。它包含每个元素的基本规则,所以在使用定制其他策略文件时,可以将它用作知识库。

    5、antisamy-tinymce.xml

    • 只允许文本格式通过,相对比较安全。

    6、antisamy.xml

    • 默认规则,允许大部分 HTML 标记,不允许 JavaScript 标记出现。

    3.3 SpringBoot 整合 AntiSamy 使用

    工程的目录结构如下:

    在这里插入图片描述

    第一步,创建 maven 工程 antiSamy_demo 并配置 pom.xml 文件

    <?xml version="1.0" encoding="UTF-8"?>
    <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.0</modelVersion>
    
        <groupId>com.hzz</groupId>
        <artifactId>antiSamy_demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.owasp.antisamy</groupId>
                <artifactId>antisamy</artifactId>
                <version>1.6.2</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </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

    第二步,创建 application.yml 文件

    server:
      port: 9000
    
    • 1
    • 2

    第三步,创建策略文件 /resources/antisamy-slashdot.xml,策略文件可以直接从 antisamy jar 包下复制

    在这里插入图片描述

    第四步,创建实体类 User

    package com.hzz.entity;
    
    import lombok.Data;
    
    @Data
    public class User {
        private int id;
        private String name;
        private int age;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第五步,创建 UserController

    package com.hzz.controller;
    
    import com.hzz.entity.User;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @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

    第六步,创建 /resources/static/index.html 页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </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

    第七步,创建过滤器,用于过滤所有提交到服务器的请求参数

    package com.hzz.filter;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    
    //过滤所有提交到服务器的请求参数
    public class XssFilter implements Filter {
        @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

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

    XssRequestWrapper 包装类实现如下

    package com.hzz.filter;
    
    import org.owasp.validator.html.*;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.UnsupportedEncodingException;
    
    public class XssRequestWrapper extends HttpServletRequestWrapper {
        /**
         * 策略文件:需要将要使用的策略文件放到项目资源文件路径
         */
        private static String antiSamyPath = XssRequestWrapper.class.getClassLoader()
                .getResource( "antisamy-ebay.xml").getFile();
    
        public static Policy policy = null;
    
        static {
            //指定策略文件
            try {
                policy = Policy.getInstance(java.net.URLDecoder.decode(antiSamyPath, "utf-8")); //我的项目路径带有中文,需要转码,否则会报错,如果你的路径都是英文则忽略转码过程
            } catch (PolicyException | UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * Antisamy 过滤数据
         * @param taintedHTML 需要进行过滤的数据
         * @Return 返回过滤后的数据
         */
        private String xssClean(String taintedHTML) {
            try {
                //使用AntiSamy 进行过滤
                AntiSamy antiSamy = new AntiSamy();
                CleanResults cr = antiSamy.scan(taintedHTML, policy);
                taintedHTML = cr.getCleanHTML();
            } catch (ScanException e) {
                e.printStackTrace();
            } catch (PolicyException e) {
                e.printStackTrace();
            }
            return taintedHTML;
        }
    
    
        public XssRequestWrapper(HttpServletRequest request) {
            super(request);
        }
    
        @Override
        public String[] getParameterValues(String name) {
            String[] values = super.getParameterValues(name);
            if (values == null) {
                return null;
            }
            int len = values.length;
            String[] newArray = new String[len];
            for (int j = 0; j < len; j++) {
                System.out.println("Antisamy 过滤清理,清理之前的参数值:"+values[j]);
                //过滤清理
                newArray[j] = xssClean(values[j]);
                System.out.println("Antisamy 过滤清理,清理之后的参数值:"+newArray[j]);
            }
            return newArray;
        }
    }
    
    • 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

    第八步,为了使上面定义的过滤器生效,需要创建配置类,用于初始化过滤器对象

    package com.hzz.config;
    
    import com.hzz.filter.XssFilter;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    //配置跨站攻击过滤器
    @Configuration
    public class AntiSamyConfiguration {
        @Bean
        public FilterRegistrationBean filterRegistrationBean() {
            FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new XssFilter());
            filterRegistrationBean.addUrlPatterns("/*");
            filterRegistrationBean.setOrder(1);
            return filterRegistrationBean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    第九步,创建启动类并启动项目,访问网站首页地址 http://localhost:9000/index.html

    package com.hzz;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @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

    第十步,输入测试数据,并观察后台打印结果

    在这里插入图片描述
    在这里插入图片描述
    从上图可以看到,测试的 XSS 攻击代码直接被清理掉了,服务器返回给浏览器的结果中没有 XSS 代码,不会被浏览器执行,成功避免了 XSS 攻击。

    升级一下:

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

    @Override
    public String getParameter(String paramString) {
        String str = super.getParameter(paramString);
        if (str == null) {
            return null;
        }
        System.out.println("Antisamy 过滤清理,清理之前的参数值:"+str);
        //过滤清理
        str = xssClean(str);
        System.out.println("Antisamy 过滤清理,清理之后的参数值:"+str);
        return str;
    }
    
    
    @Override
    public String getHeader(String paramString) {
        String str = super.getHeader(paramString);
        if (str == null) {
            return null;
        }
        System.out.println("Antisamy 过滤清理,清理之前的参数值:"+str);
        //过滤清理
        str = xssClean(str);
        System.out.println("Antisamy 过滤清理,清理之后的参数值:"+str);
        return 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++) {
                System.out.println("Antisamy 过滤清理,清理之前的参数值:"+values[i]);
                //过滤清理
                values[i] = xssClean(values[i]);
                System.out.println("Antisamy 过滤清理,清理之后的参数值:"+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
  • 相关阅读:
    MySQL案例详解 二:MHA高可用配置及故障切换
    L1-018 大笨钟
    关于 SAP Spartacus 重定向部分外部 url 到后台系统的问题
    1235. 规划兼职工作(难度:困难)
    蓝链带货怎么玩?B站横屏、竖屏恰饭竟增长1200w播放!
    Ni-IDA琼脂糖凝胶FF-------可用于纯化带组氨酸标签(His-Tag)的重组蛋白
    5G网络用户面时延测量
    零代码编程:用ChatGPT批量将Mp4视频转为Mp3音频
    最常见的Java面试题【杭州多测师_王sir】【杭州多测师】
    开发神技!阿里消息中间件进阶手册限时开源,请接住我的下巴
  • 原文地址:https://blog.csdn.net/weixin_43252521/article/details/125612344