当前位置:网站首页>AntiSamy:防 XSS 攻击的一种解决方案使用教程

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

2022-07-07 16:19:00 华仔仔coding

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>

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>

第二步,创建 application.yml 文件

server:
  port: 9000

第三步,创建策略文件 /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;
}

第五步,创建 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();
    }
}

第六步,创建 /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>

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

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);
    }
}

过滤器 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;
    }
}

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

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;
    }
}

第九步,创建启动类并启动项目,访问网站首页地址 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);
    }
}

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

在这里插入图片描述
在这里插入图片描述
从上图可以看到,测试的 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;
}
原网站

版权声明
本文为[华仔仔coding]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_43252521/article/details/125612344