提示:以下是本篇文章正文内容,下面案例可供参考
一、漏洞描述
跨站请求伪造(Cross-site request forgery,简称CSRF),是一种常见的Web安全漏洞,由于在Web请求中重要操作的所有参数可被猜测到,攻击者可通过一些技术手段欺骗用户的浏览器去访问一个用户曾经认证过的网站,遂使攻击者可冒用用户的身份,进行恶意操作。
经测试,服务器未校验Referer头,因此攻击者可以直接构造一个恶意的访问地址让用户在不知情的情况下访问从而实现CSRF恶意操作。
二、解决建议
增加拦截器,判断referer是否合法,合法则放行。
二、解决方法
Springboot 配置文件增加配置
# 信息安全
security:
#跨站点伪造请求配置
csrf:
#是否开启跨站点伪造请求过滤(开启:true 关闭:false)
enable: true
#不拦截的url
excludes-url:
- /
- /login
- /logout
- /getLoginUrl
#不拦截的域名
excludes-domain:
- localhost
- 127.0.0.1
编写配置类
/**
* @author wcs
* @desccription: 系统安全配置类
* @Date: 11:03 2022/2/17
*/
@Configuration
@ConfigurationProperties(prefix = "security")
public class SecurityConfig {
private CsrfConfig csrf;
private AccountConfig account;
public CsrfConfig getCsrf() {
return csrf;
}
public void setCsrf(CsrfConfig csrf) {
this.csrf = csrf;
}
public AccountConfig getAccount() {
return account;
}
public void setAccount(AccountConfig account) {
this.account = account;
}
}
import java.util.List;
/**
* @author wcs
* @desccription: 跨站点请求伪造
* @Date: 11:05 2022/2/17
*/
public class CsrfConfig {
/**
* 是否启用
*/
private boolean enable;
public void setEnable(boolean enable) {
this.enable = enable;
}
public boolean isEnable() {
return enable;
}
/**
* 忽略的URL
*/
private List<String> excludesUrl;
public void setExcludesUrl(List<String> excludesUrl) {
this.excludesUrl = excludesUrl;
}
public List<String> getExcludesUrl() {
return excludesUrl;
}
/**
* 忽略的domain
*/
private List<String> excludesDomain;
public void setExcludesDomain(List<String> excludesDomain) {
this.excludesDomain = excludesDomain;
}
public List<String> getExcludesDomain() {
return excludesDomain;
}
}
编写过滤器
/**
* CSRF过滤器
*
* @author wcs
*/
@Configuration
@WebFilter(filterName = "CsrfFilter", urlPatterns = "/*")
public class CsrfFilter implements Filter {
private static Logger LOGGER = LoggerFactory.getLogger(CsrfFilter.class);
/**
* 过滤器配置对象
*/
FilterConfig filterConfig = null;
@Autowired
private SecurityConfig security;
/**
* 初始化
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
security.getCsrf().getExcludesDomain().addAll(Arrays.asList(new String[]{
"localhost", "127.0.0.1"}));
}
/**
* 拦截
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
String referer = request.getHeader("Referer");
String serverName = request.getServerName();
//如果 referer 为空放行
if (StringUtils.isBlank(referer)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
URL url = null;
try {
url = new URL(referer);
} catch (MalformedURLException e) {
servletResponse.setContentType("application/json; charset=UTF-8");
servletResponse.getWriter().write(JSONUtil.toJsonStr(new ReturnT<>(ReturnT.FAIL_CODE, "系统不支持当前域名的访问!")));
LOGGER.error("域名:{}解析异常", url);
}
referer = url.getHost();
// 不启用或者已忽略的URL不拦截
if (!security.getCsrf().isEnable() || isExcludeUrl(request.getServletPath())) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if (StringUtils.isNotBlank(referer)) {
// 不启用或者已忽略的domain不拦截
if (!security.getCsrf().isEnable() || isExcludesDomain(referer)) {
filterChain.doFilter(servletRequest, servletResponse);
return;
}
}
// 判断是否存在外链请求本站
if (StringUtils.isNotBlank(referer) && referer.indexOf(serverName) < 0) {
LOGGER.error("拦截到非法请求:=> 服务器域名:{} => 非法访问域名:{}", serverName, referer);
servletResponse.setContentType("application/json; charset=UTF-8");
servletResponse.getWriter().write(JSONUtil.toJsonStr(new ReturnT<>(ReturnT.FAIL_CODE, "系统不支持当前域名的访问!")));
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
/**
* 销毁
*/
@Override
public void destroy() {
this.filterConfig = null;
}
/**
* 判断是否为忽略的URL
*
* @param urlPath URL路径
* @return true-忽略,false-过滤
*/
private boolean isExcludeUrl(String urlPath) {
if (security.getCsrf().getExcludesUrl() == null || security.getCsrf().getExcludesUrl().isEmpty()) {
return false;
}
return security.getCsrf().getExcludesUrl().stream().map(pattern -> Pattern.compile("^" + pattern)).map(p -> p.matcher(urlPath))
.anyMatch(Matcher::matches);
}
/**
* 判断是否为忽略的域名
*
* @param urlPath URL路径
* @return true-忽略,false-过滤
*/
private boolean isExcludesDomain(String urlPath) {
if (security.getCsrf().getExcludesDomain() == null || security.getCsrf().getExcludesDomain().isEmpty()) {
return false;
}
return security.getCsrf().getExcludesDomain().stream().map(pattern -> Pattern.compile("^" + pattern)).map(p -> p.matcher(urlPath))
.anyMatch(Matcher::find);
}
}