SpringBoot 防 Xss 攻击

说明

这几天自己学习了一下 SpringBoot 项目怎么预防 Xss 攻击,这里记录一下怎么防止 Xss 攻击的代码,等以后有需要用到的话,自己可以快速找到。

依赖

这里需要注意的是 1.5 到 1.9 版本有高危漏洞,需要升级到 1.10.0 以上版本,这是当前时间最新的版本。

<!--commons-text工具包,用于xss攻击对特殊参数字符做转换,1.5到1.9版本有高危漏洞-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-text</artifactId>
            <version>1.10.0</version>
        </dependency>

项目文件结构

处理非 application/json 提交方式使用下面二个类:
XssFilter 类,重写 doFilter 方法,然后调用 XssHttpServletRequestWrapper 类。
XssHttpServletRequestWrapper 类,只能对提交方式为 x-www-form-urlencoded 和 form-data 做特殊字符转换,不能对提交方式为 application/json 的数据格式做转换,如果没有指定提交方式,浏览器默认提交方式为 x-www-form-urlencoded。
处理 application/json 提交方式使用下面三个类:
XssStringJsonDeSerializer 类,这个类主要是对前端传递的数据格式为 json 格式的参数进行特殊字符转义。
XssStringJsonSerializer 类,这个类主要是处理后端向前端返回的 json 数据,将数据里面包含的特殊字符进行转义后发送。
JacksonConfig 类,调用上面 2 个自定义的序列化类,没有这个配置类,上面的 2 个类不会生效。

未处理 Xss 前的效果

测试代码:

在谷歌浏览器直接输入接口请求测试如下:
看到它这里是直接弹窗打印了,想要的返回值应该是浏览器页面中返回的是值才对。

处理非 application/json 提交方式的 xss 代码

XssFilter 类代码

package com.hjl.mall.filter;
 
import org.springframework.stereotype.Component;
 
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
 
/**
 * XSS过滤器
 */
@Component
public class XssFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /**
         * 1. 重写getParamter方法
         * 2. 在getParamter中判断属性,然后对特殊字符进行编码,使用官方提供的工具类.
         * 3. 继续提交请求
         */
        chain.doFilter(new XssHttpServletRequestWrapper(
                (HttpServletRequest) request), response);
    }
}

XssHttpServletRequestWrapper 类代码

package com.hjl.mall.filter;
 
import org.apache.commons.text.StringEscapeUtils;
 
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Objects;
 
/**
 * @ClassName XssHttpServletRequestWrapper
 * @Description 重写wrapper,只能对提交方式为x-www-form-urlencoded和form-data做特殊字符转换,不能对提交方式为json的数据格式做转换
 * @Version 1.0
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
    public XssHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }
 
    @Override
    public String getHeader(String name) {
        return StringEscapeUtils.escapeHtml4(super.getHeader(name));
    }
 
    @Override
    public String getQueryString() {
        return StringEscapeUtils.escapeHtml4(super.getQueryString());
    }
 
    @Override
    public String getParameter(String name) {
        return StringEscapeUtils.escapeHtml4(super.getParameter(name));
    }
 
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return super.getInputStream();
    }
 
    @Override
    public String[] getParameterValues(String name) {
        String[] parameterValues = super.getParameterValues(name);
        if (Objects.isNull(parameterValues)) {
            return null;
        }
        //对请求参数里面的特殊字符进行转义
        for (int i = 0; i < parameterValues.length; i++) {
            parameterValues[i] = StringEscapeUtils.escapeHtml4(parameterValues[i]);
        }
 
        return parameterValues;
    }
}

处理 Xss 后的效果

可以看到达到了自己想要的结果, 没有弹窗显示了,直接将结果正确显示到了页面中。

转义后的值如下:

但是这样对于 json 格式的数据起不到作用,如下图,发现并没有对里面的 <、>、” 这三个特殊符号进行转义:

处理 application/json 提交方式的 xss 代码

XssStringJsonDeSerializer 类代码

package com.hjl.mall.filter;
 
import java.io.IOException;
 
import org.apache.commons.text.StringEscapeUtils;
 
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
 
/**
 * 对前端传递的数据格式为json格式的参数进行特殊字符转义
 */
public class XssStringJsonDeSerializer extends JsonDeserializer<String> {
 
    @Override
    public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
        //对json格式的参数值进行转义
        return StringEscapeUtils.escapeHtml4(jsonParser.getText());
    }
}

XssStringJsonSerializer 类代码

package com.hjl.mall.filter;
 
import java.io.IOException;
import java.util.Objects;
 
import org.apache.commons.text.StringEscapeUtils;
 
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
 
/**
 * 处理后端向前端返回的json数据,将数据进行转义后发送
 */
public class XssStringJsonSerializer extends JsonSerializer<String> {
 
    @Override
    public Class<String> handledType() {
        return String.class;
    }
 
    @Override
    public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        if (Objects.nonNull(value)) {
            //将json格式数据转义后发送给前端
            String encodedValue = StringEscapeUtils.escapeHtml4(value);
            jsonGenerator.writeString(encodedValue);
        }
    }
}

JacksonConfig 类代码

package com.hjl.mall.config;
 
import com.hjl.mall.filter.XssStringJsonDeSerializer;
import com.hjl.mall.filter.XssStringJsonSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
 
/**
 * 对json格式数据做序列化和反序列化操作,防止xss攻击
 * XssFilter类
 * XssHttpServletRequestWrapper类
 * XssStringJsonDeSerializer类
 * XssStringJsonSerializer类
 * JacksonConfig类
 * 这5个类是一起的,都是用来防止xss攻击,前2个类是用来处理提交方式为x-www-form-urlencoded和form-data做特殊字符转换
 * 后面三类是对application/json提交方式做处理
 */
@Configuration
public class JacksonConfig implements WebMvcConfigurer{
 
    @Bean
    public ObjectMapper xssObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        //注入自定义的序列化工具,将@RequestBody的json格式参数做转义处理
        SimpleModule simpleModule = new SimpleModule(XssStringJsonSerializer.class.getSimpleName());
        //只对入参json进行特殊字符转义
        simpleModule.addDeserializer(String.class, new XssStringJsonDeSerializer());
        //对返回给前端的json数据(特殊字符)进行转义,这里暂时注释掉,当前项目暂不需要对返回给前端的数据进行转义
        //simpleModule.addSerializer(String.class, new XssStringJsonSerializer());
        objectMapper.registerModule(simpleModule);
        return objectMapper;
    }
}

json 格式处理 Xss 后的效果

测试代码:

因为是 post 请求, 所以这里使用 postman 工具对接口进行测试:
发现已经转义成功。

注意事项(补充说明)

这个 JacksonConfig 类 里面的代码,我注释掉了对返回 json 数据格式做转义的处理。

不注释掉的话,会进行 2 次转义处理,效果如下:
它这里先是对入参 < 转义为&lt;,然后又将&lt;里面的 & 符合在返回给前端的时候又进行了一次转义,所以变成了&amp;lt;

这一种会出现这一种情况,我这里用 txt 简单写一个 html 页面代码:

然后我将 txt 后缀改为 html,效果如下:

所以这里 JacksonConfig 类里面的 XssStringJsonSerializer 类调用需要根据自己的项目实际情况需要是否引用。

好了,笔记先做到这里,等以后自己有需要直接引用即可。