XSS:跨站脚本攻击(Cross Site Scripting),为不和 CSS混淆,故将跨站脚本攻击缩写为XSS。XSS是指恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。有点类似于sql注入。
XSS攻击原理:
HTML是一种超文本标记语言,通过将一些字符特殊地对待来区别文本和标记,例如,小于符号(<)被看作是HTML标签的开始,
模拟简单xss注入:
比如添加用户信息的form表单,可以输入用户名和密码以及手机号等表单选项,我们在输入表单数据的时候可以直接录入如下JavaScript代码
当上面的代码段被保存到数据库的时候,然后被展示出来HTML文本结构时会将代码按照编写的条件进行执行(比如循环1000编弹窗信息),从而影响用户的体验和安全性
AntiSamy是OWASP的一个开源项目,通过对用户输入的 HTML / CSS / JavaScript 等内容进行检验和清理,确保输入符合应用规范。AntiSamy被广泛应用于Web服务对存储型和反射型XSS的防御中。
AntiSamy的maven坐标:
<dependency>
<groupId>org.owasp.antisamygroupId>
<artifactId>antisamyartifactId>
<version>1.5.7version>
dependency>
<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>
可创建也可不创建
server:
port: 9000
注:AntiSamy对“恶意代码”的过滤依赖于策略文件。策略文件规定了AntiSamy对各个标签、属性的处理方法,策略文件定义的严格与否,决定了AntiSamy对XSS漏洞的防御效果。在AntiSamy的jar包中,包含了几个常用的策略文件

将AntiSamy的jar包中的
antisamy-ebay.xml文件复制到项目的resoruces资源文件中,并修改名称为antisamy-test.xml
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;
}
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();
}
}
该表单提交的内容会请求到上面编写的保存控制器
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>
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);
}
}
此时我们可以启动项目进行访问,但是还没有进行参数的过滤,所以如果我们输入任意参数都可以正常传递到Controller中,这在实际项目中是非常不安全的。为了对我们输入的数据进行过滤清理,需要通过过滤器来实现。
谷歌的浏览器对应xss攻击做了一定的识别,是会被拦截在控制台打印错误信息的,可以使用其他浏览器进行测试

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

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);
}
}
注意:通过上面的过滤器可以发现我们并没有在过滤器中直接进行请求参数的过滤清理,而是直接放行了,那么我们还怎么进行请求参数的过滤清理呢?其实过滤清理的工作是在另外一个类
XssRequestWrapper中进行的,当上面的过滤器放行时需要调用filterChain.doFilter()方法,此方法需要传入请求Request对象,此时我们可以将当前的request对象进行包装,而XssRequestWrapper就是Request对象的包装类,在过滤器放行时会自动调用包装类的getParameterValues方法,我们可以在包装类的getParameterValues方法中进行统一的请求参数过滤清理。
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);
}
}
配置拦截所有的请求过滤器,过滤器就会调用
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;
}
}
在
com.zcl.filter.XssRequestWrapper类上的【getParameterValues】方法上进行断点调试,前端页面请求几个参数就会进行几次这个方法

当前端进行表单提交的时候,后面就会拦截到

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



注意:当前我们在进行请求参数过滤时只是在包装类的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;
}
}
注意:该项目工程目录一定不能出现中文目录,否则提交数据的时候可能会出现访问antisamy-test.xml路径错误
1、提交带有javaScript的表单数据
带有JavaScript的数据会直接过滤掉
UserController save.... User(id=12, name=, age=18)
2、提交正常的表单数据
提交正常的文本不会被拦截
UserController save.... User(id=12, name=我是合法字段, age=18)
使用策略文件指定的会移除的标签如下
初略不齐全
<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"/>