原理
- 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
- 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
- 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
- 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
- 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
模拟
登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
账号:<input type="text" name="username">
密码:<input type="password" name="password">
<button type="submit">提交</button>
</form>
</body>
</html>
转账接口
@RestController
public class AccountController {
@PostMapping("/account/transfer")
public String transfer(HttpSession session,String username, int money){
String loginUser=(String)session.getAttribute("user");
if(loginUser==null) {
throw new IllegalStateException("用户为登录");
}
return loginUser+"向"+username+"转账"+money;
}
@PostMapping("/login")
public String login(HttpSession session,String username, String password){
session.setAttribute("user",username);
return "登录成功";
}
}
恶意页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js">
</script>
</head>
<body>
<form action="http://localhost:8080/account/transfer" method="post">
<input type="text" name="username" value="zansan" hidden/>
<input type="text" name="money" value="100" hidden/>
<button type="submit">查看美女图片</button>
</form>
</body>
</html>
流程总览
- 用户输入账号密码登录银行网站,服务器返回sessionID储存在cookie中
- 在未登出银行网站的情况下访问恶意网站
- 若点击链接则恶意网站会提交转账表单至银行网站,由于sessionID存储在cookie中,表单提交时也会携带cookie,服务器并不知道是恶意网站提交的表单,以为是该用户进行的操作,从而导致用户账户金额被转走。
防御措施
- 同源检测
HTTP头中有一个Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。 - token验证
登录成功后,服务端生成一个token字段,以后客户端每次请求在请求头增加token字段,服务端拦截并验证是否携待token,若未携带,则视为csrf攻击
<body>
账号:<input id="username" type="text" name="username">
密码:<input id="password" type="password" name="password">
<button id="btn">提交</button>
<script type="text/javascript">
$("#btn").click(function (){
let username=$("#username").text();
let password=$("#password").text();
$.ajax({
url:"/login",
type:"post",
xhrFields: {
withCredentials: true
},
data:{
"username":username,
"password":password
},
success:function (data) {
localStorage.setItem("user",data);
}
})
})
</script>
</body>
- 表单增加随机数
在表单中增加伪随机数字段,在验证表单时需验证是否匹配