人们经常将跨站脚本攻击(Cross Site Scripting)缩写为CSS,但这会与层叠样式表(Cascading Style Sheets,CSS)的缩写混淆。因此,有人将跨站脚本攻击缩写为XSS。跨站脚本攻击(XSS),是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。
攻击者可以使用户在浏览器中执行其预定义的恶意脚本,其导致的危害可想而知,如劫持用户会话,插入恶意内容、重定向用户、使用恶意软件劫持用户浏览器、繁殖XSS蠕虫,甚至破坏网站、修改路由器配置信息等。
解决方式有很多,这里介绍将内容经过转义然后再存储到服务器上。
package com.wssnail.xss.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 配置xss攻击过滤
* @date 2023/8/14 23:48
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List> messageConverters) {
/**
* 替换默认的MappingJackson2HttpMessageConverter,过滤(json请求参数)xss
*/
ListIterator> listIterator = messageConverters.listIterator();
while (listIterator.hasNext()) {
HttpMessageConverter> next = listIterator.next();
if (next instanceof MappingJackson2HttpMessageConverter) {
listIterator.remove();
break;
}
}
messageConverters.add(getMappingJackson2HttpMessageConverter());
}
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
// 创建自定义ObjectMapper
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new JsonHtmlXssDeserializer(String.class));
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.getApplicationContext()).build();
objectMapper.registerModule(module);
// 创建自定义消息转换器
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
return mappingJackson2HttpMessageConverter;
}
/**
* 添加默认请求类型
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.ignoreAcceptHeader(true).
defaultContentType(MediaType.APPLICATION_JSON, MediaType.TEXT_XML, MediaType.APPLICATION_XML);
}
}
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 对xss进行过滤
* @date 2023/8/14 23:49
*/
class JsonHtmlXssDeserializer extends JsonDeserializer {
public JsonHtmlXssDeserializer(Class string) {
super();
}
@Override
public Class handledType() {
return String.class;
}
@Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (!StringUtils.isEmpty(value)) {
return StringEscapeUtils.escapeHtml4(value);
}
return value;
}
}
请求如上接口,经过转义之后,存到数据库的内容就不再是我们传参的数据,而是经过转义的内容
经过查询之后内容就回显成转义之后的内容了,如下图
先说一下思路,自定义一个注解,作用在实体类或者字段上,当查询的时候,在数据返回之前,对数据进行反转义。下面直接上代码。
自定义注解
package com.wssnail.xss.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StringEscapeCode {
}
工具类
package com.wssnail.xss.utils;
import com.wssnail.xss.annotation.StringEscapeCode;
import org.apache.commons.text.StringEscapeUtils;
import java.lang.reflect.Field;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 配置字符串格式文本处理工具
* @date 2023/8/14 23:50
*/
public class StringEscapeCodeUtil {
public static void stringEscape(Class> clazz, Object obj) throws IllegalAccessException {
StringEscapeCode declaredAnnotation = clazz.getDeclaredAnnotation(StringEscapeCode.class);
if (declaredAnnotation != null) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getGenericType().toString().endsWith("String")) {
//有注解,设置字段
fields[i].setAccessible(true);
String value = StringEscapeUtils.unescapeHtml4((String) fields[i].get(obj));
fields[i].set(obj, value);
}
}
} else {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
StringEscapeCode annotation = fields[i].getAnnotation(StringEscapeCode.class);
if (fields[i].getGenericType().toString().endsWith("String")) {
if (annotation != null) {
//有注解,设置字段
fields[i].setAccessible(true);
String value = StringEscapeUtils.unescapeHtml4((String) fields[i].get(obj));
fields[i].set(obj, value);
}
}
}
}
}
}
实际使用
package com.wssnail.xss.annotation;
import java.lang.annotation.*;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 自定义注解
* 使用方法 将该注解添加到类上,或者属性上,只有作用在、string类型的属性上,注解才会生效
* @date 2023/8/15 0:13
*/
@Target({ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface StringEscapeCode {
}
public User getById(Integer id) {
try {
User user = userMapper.findByID(id);
Class extends User> clazz = user.getClass();
StringEscapeCodeUtil.unStringEscape(clazz, user);
return user;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
然后查询的时候,就会正常显示内容了。
完整代码 xss-demo: 解决xss转义乱码返回的问题