SpringBoot 增加 XSS 跨站脚本攻击防护
XSS 原理
XSS 攻击的原理是利用前后端校验不严格,用户将攻击代码植入到数据中提交到了后台,当这些数据在网页上被其他用户查看的时候触发攻击。
举例:用户提交表单时把地址写成:
杭州市<script>for(var i=0;i<9999;i++){alert(i)}</script>
上面的数据如果没有在后台做处理,当数据被展示到网页上的时候,会在网页上弹出 N 个 alert 框,当然实际攻击肯定是比这个要复杂的多的。
项目代码
本文完整项目代码位于:https://github.com/Jueee/blog-project/tree/main/java-web-xss
XSS 攻击示例
代码示例
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Controller 如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
public class DemoController {
@RequestMapping("/demo")
public String demo(HttpServletRequest request, HttpServletResponse response, ModelMap m) {
log.info("into demo page");
return "demo";
}
@ResponseBody
@RequestMapping("/demoAction")
public String demoAction(@RequestParam(value = "name") String name,
HttpServletRequest request, HttpServletResponse response, ModelMap m) {
log.info("name:"+name);
return "name is:"+name;
}
}
前端页面如下:
<form action="demoAction" method="post" name="demoForm">
Name: <input type="text" name="name" value="demo"/>
<input type="submit" value="Submit" />
</form>
正常效果
访问 http://127.0.0.1:8080/demo
点击提交:
攻击效果
访问 http://127.0.0.1:8080/demo,并输入
test<script>alert('Attack!')</script>
点击提交:
SpringBoot 防护
添加依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version>
</dependency>
Filter 类
import org.apache.commons.lang3.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class XssFilter implements Filter {
private List<String> excludes = new ArrayList<>();
private boolean enabled = false;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String strExcludes = filterConfig.getInitParameter("excludes");
String strEnabled = filterConfig.getInitParameter("enabled");
//将不需要xss过滤的接口添加到列表中
if(StringUtils.isNotEmpty(strExcludes)){
String[] urls = strExcludes.split(",");
for(String url:urls){
excludes.add(url);
}
}
if(StringUtils.isNotEmpty(strEnabled)){
enabled = Boolean.valueOf(strEnabled);
}
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
//如果该访问接口在排除列表里面则不拦截
if(isExcludeUrl(request.getServletPath())){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//拦截该url并进行xss过滤
XssHttpServletRequestWrapper xssHttpServletRequestWrapper = new XssHttpServletRequestWrapper(request);
filterChain.doFilter(xssHttpServletRequestWrapper,servletResponse);
}
@Override
public void destroy() {
}
private boolean isExcludeUrl(String urlPath){
if(!enabled){
//如果xss开关关闭了,则所有url都不拦截
return true;
}
if(excludes==null||excludes.isEmpty()){
return false;
}
String url = urlPath;
for(String pattern:excludes){
Pattern p = Pattern.compile("^"+pattern);
Matcher m = p.matcher(url);
if(m.find()){
return true;
}
}
return false;
}
}
XSS 过滤包装类
增加一个 XssHttpServletRequestWrapper 类,这个类重写了获取参数的方法,在获取参数时做了 XSS 替换处理
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.safety.Whitelist;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* xss过滤包装类
*/
@Slf4j
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
/**
* Constructs a request object wrapping the given request.
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public XssHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getHeader(String name) {
String strHeader = super.getHeader(name);
if(StringUtils.isEmpty(strHeader)){
return strHeader;
}
return Jsoup.clean(super.getHeader(name),Whitelist.relaxed());
}
@Override
public String getParameter(String name) {
String strParameter = super.getParameter(name);
if(StringUtils.isEmpty(strParameter)){
return strParameter;
}
return Jsoup.clean(super.getParameter(name),Whitelist.relaxed());
}
@Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if(values==null){
return values;
}
int length = values.length;
String[] escapseValues = new String[length];
for(int i = 0;i<length;i++){
//过滤一切可能的xss攻击字符串
escapseValues[i] = Jsoup.clean(values[i], Whitelist.relaxed()).trim();
if(!StringUtils.equals(escapseValues[i],values[i])){
log.info("xss字符串过滤前:"+values[i]+"\t"+"过滤后:"+escapseValues[i]);
}
}
return escapseValues;
}
}
配置加载类
SpringBoot 里面增加一个 configuration 配置,把 Filter 类配置上去
import com.jueee.utils.XssFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.DispatcherType;
import java.util.HashMap;
import java.util.Map;
/**
* 设置跨站脚本过滤
*/
@Configuration
public class FilterConfig {
@Value("${xss.enabled}")
private String enabled;
@Value("${xss.excludes}")
private String excludes;
@Value("${xss.urlPatterns}")
private String urlPatterns;
@Bean
public FilterRegistrationBean xssFilterRegistration(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
registrationBean.setFilter(new XssFilter());
registrationBean.addUrlPatterns(StringUtils.split(urlPatterns,","));
registrationBean.setName("XssFilter");
registrationBean.setOrder(9999);
Map<String,String> initParameters = new HashMap<>();
initParameters.put("excludes",excludes);
initParameters.put("enabled",enabled);
registrationBean.setInitParameters(initParameters);
return registrationBean;
}
}
配置文件
在 application.properties 或者 application.yml 里面增加一些开关配置,可以忽略某些接口提交的数据或者关闭 xss 过滤
#xss攻击拦截
xss.enabled=true
xss.excludes=
xss.urlPatterns=/*
防护效果
访问 http://127.0.0.1:8080/demo,输入 test<script>alert('Attack!')</script>
,并提交:
日志:
: into demo page
: xss字符串过滤前:test<script>alert('Attack!')</script> 过滤后:test
: name:test
相关文章