跨站点请求伪造攻击的原理及防御

攻击原理

跨站点请求伪造(Cross Site Request Forgery,简称CSRF)能够形成的根本原因是利用了浏览器cookie自动发送的特点。如果浏览器cookie中保存了用户信息,该攻击利用了cookie共享机制获取了用户信息,因此可以正常通过系统的鉴权认证,实现非法操作。
下面以网上银行转账的例子来说明该攻击的流程。
1.小明在网上银行(bank.com)转账,浏览器cookie记录了小明的用户信息。
2. 小明在未关闭浏览器的情况下,又新建了页面开始浏览帖子(sns.com)。
3. 小黑是个黑客,已经将伪造的请求链接包含在sns.com的页面中。
4. 小明点中了小黑提供的伪造链接,那么该请求将会使用cookie中的合法信息,通过了银行的认证系统,实现小黑的非法操作。
这样就实现了跨站点请求伪造攻击。

防御措施

解决跨站点请求伪造攻击,一般有以下两种方法:

  1. Referer校验
  2. Token校验
    2.1、referer校验
    在 HTTP 协议的请求头部含有一个字段叫 Referer,它记录了本次请求的来源地址。例如从A网站跳到B网站,那么在B网站的看到的referer值就是A网站的域名。

在上面的银行转账例子的攻击中,银行系统接收到的攻击请求的referer是sns.com。如果该系统增加了referer校逻辑,就可以判断本次请求是否来自本系统bank.com,如果不是则拒绝。因此如

果银行系统看到请求的referer是sns.com,则可以拒绝访问。注意,第一次访问时,referer值会为空。
所以referer校验的策略是,允许referer值是本服务域名和空值的请求通过。
我们可以采用过滤器进行统一拦截和校验,代码如下:

package com.***.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;

@Order(1)
@WebFilter(filterName = "CSRFFilter", urlPatterns = "/*")
public class CSRFFilter implements Filter {
	private static final Logger logger = LoggerFactory.getLogger(CSRFFilter.class);

	@Override
	public void init(FilterConfig config) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// System.out.println("==============CSRFFilter=================");
		HttpServletRequest req = (HttpServletRequest) request;
		HttpServletResponse resp = (HttpServletResponse) response;

		if (checkReferer(req, resp)) {
			// 拦截
			response.setContentType("text/html;charset=utf-8");
			logger.error("跨站点请求伪造攻击,系统已拦截");
			throw new ServletException("非法请求!");
			// return;
		}
		// 放行
		chain.doFilter(req, resp);
	}

	private boolean checkReferer(HttpServletRequest request, HttpServletResponse response) {
		String referer = request.getHeader("referer");
		String serviceName = request.getServerName();

		if (null == referer) {
			return false;
		}
		if (referer != null && referer.matches("^(http|https)+://(" + serviceName + ")(.*)")) {
			// 本系统的访问 直接放行
			return false;
		}		
		if (referer != null && referer
				.matches("^[(http)|(https)]+://(localhost|10\\.|172\\.|127\\.0\\.0\\.1)+(.*)")) {
			// 内网的访问 直接放行
			return false;
		}
		logger.error("referer:{}", referer);
		return true;
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
	}
}

2.2、Token校验
token校验的原理是用户第一次登陆成功之后,系统给用户颁发随机且唯一的token,用户下次请求的时候,将该token作为参数传到后台,后台通过过滤器或者拦截器校验该token是否合法。若合法,则放行,不合法则拒绝访问。前端可以把token值存在请求头或者请求参数中,都可以。
后台校验逻辑如下:

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest req = (HttpServletRequest) request;
		HttpSession s = req.getSession();
		// 从 session 中得到 csrftoken 属性
		String sToken = (String) s.getAttribute("csrftoken");
		if (sToken == null) {
			// 产生新的 token 放在session 中
			sToken = generateToken();
			s.setAttribute("csrftoken", sToken);
			chain.doFilter(request, response);
		} else {
			// 从 HTTP 头中取得 csrftoken
			String xhrToken = req.getHeader("csrftoken");
			// 从请求参数中取得 csrftoken
			String pToken = req.getParameter("csrftoken");
			if (sToken != null && xhrToken != null && sToken.equals(xhrToken)) {
				chain.doFilter(request, response);
			} else if (sToken != null && pToken != null && sToken.equals(pToken)) {
				chain.doFilter(request, response);
			} else {
				request.getRequestDispatcher("error.jsp").forward(request, response);
			}
		}
	}
	```

你可能感兴趣的:(spring)