概念
定义
CSRF跨站点请求伪造(Cross—Site Request Forgery):顾名思义,攻击者伪造用户请求请求服务端,但并非在官方站点发起,而是攻击者的站点,或者攻击者的脚本。而服务器认为却是合法的,有点借刀杀人的意思。
原理
攻击者伪造的网站,或者伪造请求作为一个链接或者按钮。提供给正常用户点击或者在其他站点提交,请求的地址是官方服务器,携带的是当前操作人的Cookie信息。导致操作的内容为攻击者的想要的,达成攻击目标。
案例
电商网站,商品详情页往往有评论,攻击者制作一个点击按钮,用户点击就能对自己下过单的商品进行评论,评论的内容和动作都是攻击者来设定。对网站和个人的名誉产生负面影响。
防御方案
体验与安全兼顾:在请求中添加 token 并验证。
- token hidden在页面,对用户透明。
- 攻击者无法获取到token和伪造。
解决思路
- 首先将这种操作类请求做成post请求。避免URL上带token,防止获取Referer得到token
- token是在进入操作页面时由服务端生成并埋入。提交时携带token,服务端对token进行校验,确定请求有效性
- token要求:与请求用户有唯一绑定关系,防止伪造。
具体实现
redis + session 实现token (集群情况下要求分布式session实现方案)
- 用户请求,服务端获取当前用户sessionId;同时生成一个UUID,将UUID作为key sessionId作为Value存入redis。目的:特定发起者有唯一的token,攻击者不能模仿请求获取到token来伪造。
- 将UUID作为Token 返回给用户页面,用户提交时携带UUID作为入参,传递到服务端。
- 服务端获取操作请求,利用UUID获取SessionId 和本次请求的sessionId比对,通过校验之后才放过 。不一致则拒绝。
- 利用sessionId的理由:就算攻击者模仿请求获取到UUID的token给到用户提交的sessionId是不同的,起不到攻击作用
- 注意:不要钻请求劫持的牛角尖,如果用户请求时被劫持了,这种情况不止是CSRF那么简单了,啥都能发生。
关键代码
token
public void generateToken(){
String userToken = UUID.randomUUID().toString();
String sessionId = request.getSession().getId();
RedisUtils.put(userToken,sessionId);
}
check token
public void checkToken(){
String sessionId = request.getSession().getId();
String userSessionId = RedisUtils.get(userToken);
if (sessionId == null || !sessionId.equals(userSessionId)){
// false;
}
// go
}