csrf介绍以及原理
CSRF(Cross-Site Request Forgery) ,通过单词简单理解就是跨站点请求伪造,用通俗简单点就是攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的操作
1、用户打开浏览器访问受信任网站A,输入用户名和密码请求登录网站A;
2、在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3、用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4、网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5、浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
CSRF 攻击的三个条件 :
1 . 用户已经登录了站点 A,并在本地记录了 cookie
2 . 在用户没有登出站点 A 的情况下(也就是 cookie 生效的情况下),访问了恶意攻击者提供的引诱危险站点 B (B 站点要求访问站点A)。
3 . 站点 A 没有做任何 CSRF 防御
如果想详细了解,可以参考看下这些文章
什么是CSRF?如何防御CSRF攻击?知了堂告诉你 - 知乎
https://juejin.cn/post/7008171429845811207#comment
springboot项目怎么实现防御
这里我们没有使用spring security框架来实现,如果使用该框架来实现,可以略过此文
后端代码添加
pom文件引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.18</version>
</dependency>
启动类添加filter扫描
@ServletComponentScan(basePackages = {"com.filter.Referer"})
public class MyWebApplication {
public static void main(String[] args) {
SpringApplication.run(MyWebApplication.class, args);
}
}
新增过滤器
工具类RedisEngine
@Component
public class RedisEngine {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void put(String key, String value, int timeout, TimeUnit unit) {
stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
}
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
public boolean containsKey(String key) {
return stringRedisTemplate.redisTemplate.hasKey(key);
}
}
1.新增header头部添加token机制校验
@Slf4j
@Order(value = 99)
@WebFilter(filterName = "csrfTokenFilter", urlPatterns = {"/*"})
public class CSRFTokenFilter implements Filter {
private static final String CSRF_TOKEN_NAME = "csrfToken";
private static final String CSRF_TOKEN_ = "_C_T_";
@Autowired
private RedisEngine redisEngine;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String csrfToken = httpRequest.getHeader(CSRF_TOKEN_NAME);
if (csrfToken == null || !csrfToken.equals(redisEngine.get(CSRF_TOKEN_))) {
Map<String, Object> rsp = new HashMap<>(2);
rsp.put("code", 403);
rsp.put("msg", "非法伪造访问");
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json; charset=utf-8");
final PrintWriter writer = httpResponse.getWriter();
writer.write(JSON.toJSONString(rsp));
writer.flush();
writer.close();
return;
}
chain.doFilter(httpRequest, httpResponse);
}
}
2.新增登录生成token来验证,当然也可以通过新增获取token的接口,每次请求动态生成token
@Slf4j
@Order(value = 98)
@WebFilter(filterName = "loginSetTokenFilter", urlPatterns = {"/login"})
public class LoginSetTokenFilter implements Filter {
private static final String CSRF_TOKEN_ = "_C_T_";
@Autowired
private RedisEngine redisEngine;
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!redisEngine.containsKey(CSRF_TOKEN_)) {
//设置请求头信息
// 生成新的CSRF Token
String csrfToken = UUID.randomUUID().toString();
((HttpServletRequest) req).getSession().setAttribute(CSRF_TOKEN_, csrfToken);
redisEngine.put(CSRF_TOKEN_, csrfToken, 350, TimeUnit.MINUTES);
}
filterChain.doFilter(request, response);
}
}
前端代码添加
// 获取Cookie的值
function getCookie(name) {
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) {
return parts.pop().split(";").shift();
}
}
$.ajax({
url : enableUrl,
async : true,
dataType : 'json',
type : 'POST',
headers:{
"csrfToken":getCookie('_C_T_')
},
data : {
'userNames' : "xxxxx" },
success : function(data) {
//todo
},
error : function() {
//todo
},
});
使用Burpsuite工具来复测
看到这里,说明我们已经实现了CSRF的防御功能
如果对于使用durpsuite工具不熟悉的可以看上一篇文章