需求:xxs攻击过滤
测试发现代码转换成图片格式后,可以通过上传文件接口存在服务器上,再次打开时候会执行代码
项目背景:前端采用form+ajax提交数据,后端采用SpringMVC框架,@RequestMapping注解的方法接收前端参数。其中还有用到multipart/form-data传输文件。
思路:
一、实现XSSFilter类(包括工具类和multipartResolver的使用)
二、实现XssHttpServletRequestWraper类
三、注册过滤器(配置类XssConfig和web.xml两种方式)
1.创建过滤器类
1.1XssFilter
在XssFilter需要使用ApplicationContext在初始化filter时候注入multipartResolver,
所以filter需ApplicationContextUtils工具类。filter初始化建立在ApplicationContextUtils加载的基础上,所以需要注释
@DependsOn({"applicationContextUtils"})
详情见SpringBoot使用ApplicationContext.getBean启动报空指针处理记录
package xxxx.web.filter.xss;
import xxxx.web.utils.ApplicationContextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.MultipartResolver;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.*;
import java.io.IOException;
@Component
@DependsOn({"applicationContextUtils"})
public class XssFilter implements Filter{
// 为了解决multipart/form-data过滤器过滤之后controller接收不到数据的问题
@Autowired
private MultipartResolver multipartResolver=null;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//注入bean
multipartResolver=(MultipartResolver) ApplicationContextUtils.getApplicationContext().getBean("multipartResolver",MultipartResolver.class);
}
@Override
public void doFilter(ServletRequest request , ServletResponse response , FilterChain chain)
throws IOException, ServletException{
String contentType=request.getContentType();
if(contentType!=null && contentType.contains("multipart/form-data")){
//form-data过滤
MultipartHttpServletRequest multipartRequest=multipartResolver.resolveMultipart((HttpServletRequest) request);
XssHttpServletRequestWraper xssRequest=new XssHttpServletRequestWraper(multipartRequest);
chain.doFilter(xssRequest, response);
}else{
//普通过滤
XssHttpServletRequestWraper xssRequest=new XssHttpServletRequestWraper((HttpServletRequest)request);
chain.doFilter(xssRequest, response);
}
}
@Override
public void destroy(){
}
}
1.2 工具类ApplicationContextUtils
package xxx.web.utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import java.util.Locale;
//对Spring容器进行各种上下文操作的工具类
@Configuration
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
ApplicationContextUtils.context = context;
}
public static ApplicationContext getApplicationContext(){
return context;
}
//根据Bean名称获取Bean对象
public static Object getBean(String beanName){
return context.getBean(beanName);
}
public static Object getMassage(String key){
return context.getMessage(key, null, Locale.getDefault());
}
}
1.3multipartResolver文件上传配置
我感觉上传文件可以做到零配置,但是此处项目用到的是不限制文件大小,仅供参考
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=-1
spring.servlet.multipart.max-request-size=-1
很多文章中此处用了xml方式
<!-- =========================注册文件上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 文件上传大小限制 -->
<property name="maxUploadSize">
<value>104857600</value>
</property>
<property name="defaultEncoding">
<value>UTF-8</value>
</property>
</bean>
文件上传使用multipartResolver具体解析如下:
2.XssHttpServletRequestWraper
package xxx.web.filter.xss;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class XssHttpServletRequestWraper extends HttpServletRequestWrapper {
public XssHttpServletRequestWraper(HttpServletRequest servletRequest){
super(servletRequest);
}
@Override
public String getHeader(String name){
return super.getHeader(name);
}
@Override
public String getParameter(String name){
String value = super.getParameter(name);
return xssEncode(value);
}
//对以FormData形式提交,Content-Type:application/x-www-from-urlencoded参数过滤
@Override
public String[] getParameterValues(String name){
String[] values=super.getParameterValues(name);
if(values==null){
return null;
}
int count=values.length;
String[] encodeValues=new String[count];
for(int i=0;i<count;i++){
encodeValues[i]=xssEncode(values[i]);
}
return encodeValues;
}
/*过滤策略:把特殊字符转为HTML实体编码,
*这样存在数据库里较安全
*返回给前端时会被js解析为正常字符,不影响查看*/
public static String xssEncode(String str){
if(str == null || str.isEmpty()){
return str;
}
str = str.replaceAll(";", ";");
str = str.replaceAll("<", "<").replaceAll(">", ">");
str = str.replaceAll("\\(", "(").replaceAll("\\)", ")");
str = str.replaceAll("'", "'").replaceAll("\"", """);
str = str.replaceAll("\\$", "$");
str = str.replaceAll("%", "%");
str = str.replaceAll("\\/", "/").replaceAll("\\\\", "\");
str = str.replaceAll(":", ":");
str = str.replaceAll("\\?", "?").replaceAll("@", "@");
str = str.replaceAll("\\^", "^");
return str;
}
}
3.注册过滤器
3.1配置类XssConfig
在这个配置类中,我们通过FilterRegistrationBean来注册XssFilter。setOrder方法用于设置过滤器的执行顺序,addUrlPatterns方法用于指定过滤器应该应用于哪些URL模式,setDispatcherTypes方法用于指定过滤器应该拦截的请求类型(如REQUEST、FORWARD、INCLUDE、ERROR等)。
package xxx.web.filter.xss;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import javax.servlet.DispatcherType;
import org.springframework.context.annotation.Configuration;
@Configuration
public class XssConfig {
@Autowired
private XssFilter xssFilter;
@Bean
public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(xssFilter);
registration.setOrder(Integer.MAX_VALUE); // 设置过滤器顺序
registration.addUrlPatterns("/*"); // 设置过滤器的URL模式
registration.setDispatcherTypes(DispatcherType.REQUEST); // 指定过滤器应该拦截的类型
return registration;
}
}
3.2web.xml配置过滤器
<filter>
<filter-name>XssFilter</filter-name>
<filter-class>XssFilter在项目存放的位置</filter-class>
</filter>
<filter-mapping>
<filter-name>XssFilter</filter-name>
<url-partten>/*</url-partten>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
结果:
<script>alert("1");</script>
后台会解析为<script>alert("1");</script>
并存入数据库,而在前端显示时又会显示为原来的字符,且不会执行js语句。
返回给前端时,如果是html(str)显示,则能显示原来字符。如果是val(str)返回给文本框的话,则并不会把HTML实体字符解析为正常字符。所以需要我们写个解析函数。在此略