SpringBoot2.x 整合 AntiSamy防御XSS攻击的简单总结

网友投稿 1519 2022-12-14

SpringBoot2.x 整合 AntiSamy防御XSS攻击的简单总结

SpringBoot2.x 整合 AntiSamy防御XSS攻击的简单总结

目录一、引入依赖二、策略文件三、实体类和Controller四、创建过滤器五、创建XssRequestWrapper类六、创建配置类七、测试代码示例

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

XSS攻击全称为跨站脚本攻击(Cross Site Scripting),是一种在web应用中的计算机安全漏洞,它允许用户将恶意代码(如script脚本)植入到Web页面中,为了不和层叠样式表(Cascading Style Sheets, CSS)混淆,一般缩写为XSS。XSS分为以下两种类型:

存储型XSS:服务端对用户输入的恶意脚本没有经过验证就存入数据库,每次调用数据库都会将其渲染在浏览器上。则可能为存储型XSS。

反射型XSS:通过get或者post等方式,向服务端输入数据。如果服务端不进行过滤,验证或编码,直接将用户信息呈现出来,可能会造成反射型XSS。

本文主要对SpringBoot2.x集成AntiSamy防御XSS攻击进行简单总结,其中SpringBoot使用的2.4.5版本。

一、引入依赖

org.springframework.boot

spring-boot-starter-web

org.owasp.antisamy

antisamy

1.6.2

org.projectlombok

lombok

1.18.8

org.apache.commons

commons-text

1.9

二、策略文件

Antisamy对恶意代码的过滤依赖于策略文件,策略文件为xml格式,规定了AntiSamy对各个标签、属性的处理方法。策略文件定义的严格与否,决定了AntiSamy对Xss的防御效果。在AntiSamy的jar包中,已经包含了几个常用的策略文件:

本文使用antisamy-ebay.xml作为策略文件,该策略相对安全,适用于电商网站。将antisamy-ebay.xml和antisamy.xsd复制到resouces目录下。对于策略文件的具体内容这里不进行深入了解,只需了解下对标签的处理规则,共有remove、truncate、validate三种处理方式,其中remove为直接删除,truncate为缩短标签,只保留标签和值,validate为验证标签属性:

上图截取了的一部分,可知对script标签的处理策略是remove。

三、实体类和Controller

用户实体类:

package com.rtxtitanv.model;

import lombok.AllArgsConstructor;

import lombok.Data;

import lombok.NoArgsConstructor;

/**

* @author rtxtitanv

* @version 1.0.0

* @name com.rtxtitanv.model.User

* @description 用户实体类

* @date 2021/8/23 14:54

*/

@AllArgsConstructor

@NoArgsConstructor

@Data

public class User {

private Long id;

private String username;

private String password;

}

Controller:

package com.rtxtitanv.controller;

import com.rtxtitanv.model.User;

import org.springframework.web.bind.annotation.*;

/**

* @author rtxtitanv

* @version 1.0.0

* @name com.rtxtitanv.controller.UserController

* @description UserController

* @date 2021/8/23 14:54

*/

@RequestMapping("/user")

@RestController

public class UserController {

@PostMapping("/save")

public User saveUser(User user) {

return user;

}

@GetMapping("/get")

public User getUserById(@RequestParam(value = "id") Long id) {

return new User(id, "ZhaoYun", "123456");

}

@PutMapping("/update")

public User updateUser(@RequestBody User user) {

return user;

}

}

四、创建过滤器

package com.rtxtitanv.filter;

import com.rtxtitanv.wrapper.XssRequestWrapper;

import javax.servlet.*;

import javax.servlet.http.HttpServletRequest;

import java.io.IOException;

/**

* @author rtxtitanv

* @version 1.0.0

* @name com.rtxtitanv.filter.XssFilter

* @description XSS过滤器

* @date 2021/8/23 15:01

*/

public class XssFilter implements Filter {

private FilterConfig filterConfig;

@Override

public void init(FilterConfig filterConfig) throws ServletException {

this.filterConfig = filterConfig;

}

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException, ServletException {

// 拦截请求,处理XSS过滤

chain.doFilter(new XssRequestWrapper((HttpServletRequest)request), response);

}

@Override

public void destroy() {

this.filterConfig = null;

}

}

注意:在过滤器中并没有直接对请求参数进行过滤清洗,而是在XssRequestWrapper类中进行的。XssRequestWrapper类将当前的request对象进行了包装,在过滤器放行时会自动调用XssRequestWrapper中的方法对请求参数进行清洗。

五、创建XssRequestWrapper类

package com.rtxtitanv.wrapper;

import com.fasterxml.jackson.core.jsonGenerator;

import com.fasterxml.jackson.databind.JsonSerializer;

import com.fasterxml.jackson.databind.SerializerProvider;

import org.apache.commons.lang3.StringUtils;

import org.apache.commons.text.StringEscapeUtils;

import org.owasp.validator.html.*;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

import java.io.IOException;

import java.io.UnsupportedEncodingException;

import java-.URLDecoder;

import java.util.Map;

import java.util.Objects;

/**

* @author rtxtitanv

* @version 1.0.0

* @name com.rtxtitanv.wrapper.XssRequestWrapper

* @description 装饰器模式加强对request的处理,基于AntiSamy进行XSS防御

* @date 2021/8/23 15:01

*/

public class XssRequestWrapper extends HttpServletRequestWrapper {

private static final Logger LOGGER = LoggerFactory.getLogger(XssRequestWrapper.class);

private static Policy policy = null;

static {

try {

// 获取策略文件路径,策略文件需要放到项目的classpath下

String antiSamyPath = Objects

.requireNonNull(XssRequestWrapper.class.getClassLoader().getResource("antisamy-ebay.xml")).getFile();

LOGGER.info(antiSamyPath);

// 获取的文件路径中有空格时,空格会被替换为%20,在new一个File对象时会出现找不到路径的错误

// 对路径进行解码以解决该问题

antiSamyPath = URLDecoder.decode(antiSamyPath, "utf-8");

LOGGER.info(antiSamyPath);

// 指定策略文件

policy = Policy.getInstance(antiSamyPath);

} catch (UnsupportedEncodingException | PolicyException e) {

e.printStackTrace();

}

}

public XssRequestWrapper(HttpServletRequest request) {

super(request);

}

/**

* 过滤请求头

*

* @param name 参数名

* @return 参数值

*/

@Override

public String getHeader(String name) {

String header = super.getHeader(name);

// 如果Header为空,则直接返回,否则进行清洗

return StringUtils.isBlank(header) ? header : xssClean(header);

}

/**

* 过滤请求参数

*

* @param name 参数名

* @return 参数值

*/

@Override

public String getParameter(String name) {

String parameter = super.getParameter(name);

// 如果Parameter为空,则直接返回,否则进行清洗

return StringUtils.isBlank(parameter) ? parameter : xssClean(parameter);

}

/**

* 过滤请求参数(一个参数可以有多个值)

*

* @param name 参数名

* @return 参数值数组

*/

@Override

public String[] getParameterValues(String name) {

String[] parameterValues = super.getParameterValues(name);

if (parameterValues != null) {

int length = parameterValues.length;

String[] newParameterValues = new String[length];

for (int i = 0; i < length; i++) {

LOGGER.info("AntiSamy清理之前的参数值:" + parameterValues[i]);

// 清洗参数

newParameterValues[i] = xssClean(pAJYblzarameterValues[i]);

LOGGER.info("AntiSamy清理之后的参数值:" + newParameterValues[i]);

}

return newParameterValues;

}

return super.getParameterValues(name);

}

@Override

public Map getParameterMap() {

Map requestMap = super.getParameterMap();

requestMap.forEach((key, value) -> {

for (int i = 0; i < value.length; i++) {

LOGGER.info(value[i]);

value[i] = xssClean(value[i]);

LOGGER.info(value[i]);

}

});

return requestMap;

}

/**

* 使用AntiSamy清洗数据

*

* @param value 需要清洗的数据

* @return 清AJYblz洗后的数据

*/

private String xssClean(String value) {

try {

AntiSamy antiSamy = new AntiSamy();

// 使用AntiSamy清洗数据

final CleanResults cleanResults = antiSamy.scan(value, policy);

// 获得安全的HTML输出

value = cleanResults.getCleanHTML();

// 对转义的HTML特殊字符(<、>、"等)进行反转义,因为AntiSamy调用scan方法时会将特殊字符转义

return StringEscapeUtils.unescapeHtml4(value);

} catch (ScanException | PolicyException e) {

e.printStackTrace();

}

return value;

}

/**

* 通过修改Json序列化的方式来完成Json格式的XSS过滤

*/

public static class XssStringJsonSerializer extends JsonSerializer {

@Override

public Class handledType() {

return String.class;

}

@Override

public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

if (!StringUtils.isBlank(value)) {

try {

AntiSamy antiSamy = new AntiSamy();

final CleanResults cleanResults = antiSamy.scan(value, XssRequestWrapper.policy);

gen.writeString(StringEscapeUtils.unescapeHtml4(cleanResults.getCleanHTML()));

} catch (ScanException | PolicyException e) {

e.printStackTrace();

}

}

}

}

}

六、创建配置类

package com.rtxtitanv.config;

import com.fasterxml.jackson.databind.ObjectMapper;

import com.fasterxml.jackson.databind.module.SimpleModule;

import com.rtxtitanv.filter.XssFilter;

import com.rtxtitanv.wrapper.XssRequestWrapper;

import org.springframework.boot.web.servlet.FilterRegistrationBean;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import javax.servlet.Filter;

/**

* @author rtxtitanv

* @version 1.0.0

* @name com.rtxtitanv.config.AntiSamyConfig

* @description AntiSamy配置类

* @date 2021/8/23 15:05

*/

@Configuration

public class AntiSamyConfig {

/**

* 配置XSS过滤器

*

* @return FilterRegistrationBean

*/

@Bean

public FilterRegistrationBean filterRegistrationBean() {

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean<>(new XssFilter());

filterRegistrationBean.addUrlPatterns("/*");

filterRegistrationBean.setOrder(1);

return filterRegistrationBean;

}

/**

* 用于过滤Json类型数据的解析器

*

* @param builder Jackson2ObjectMapperBuilder

* @return ObjectMapper

*/

@Bean

public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {

// 创建解析器

ObjectMapper objectMapper = builder.createXmlMapper(false).build();

// 注册解析器

SimpleModule simpleModule = new SimpleModule("XssStringJsonSerializer");

simpleModule.addSerializer(new XssRequestWrapper.XssStringJsonSerializer());

objectMapper.registerModule(simpleModule);

return objectMapper;

}

}

七、测试

启动项目,发送如下POST请求,请求地址为http://localhost:8080/user/save,可见表单参数中的0,可见Query参数中的

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:从实战角度详解Disruptor高性能队列
下一篇:springsecurity中http.permitall与web.ignoring的区别说明
相关文章

 发表评论

暂时没有评论,来抢沙发吧~