打V最近重新温习了一遍CSRF,决定写个博客简单记录一下CSRF的原理,方便以后复习之用。
CSRF
CSRF的全称为 Cross-site request forgery,中文称为跨站请求伪造。顾名思义就是其他非法网站向合法网站发送被伪造的用户请求。
经典场景
CSRF的其中一个经典场景就是银行网银转账。
- 用户Bob登录了网银,并转了100元给朋友,转账完成之后Bob并没有点击登出网银。
网银系统发送了如下的请求。
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100&account={friend_account}
从中我们可以看到该请求包含了转账金额,以及接受转账的账户是朋友的账户 friend_account。
特别值得注意的是,该网银系统是通过Cookie来验证用户的身份的。
- 用户Bob无意间点进了攻击者的网站,网站中隐藏的JavaScript也向网银系统发送了一模一样的请求,只不过收款账户变成了攻击者的账户了。
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded
amount=100&account={attacker_account} // 篡改为了攻击者账户
于是,在神不知鬼不觉的情况下,Bob就这么莫名其妙地给攻击者打款了100元。
为什么会发生这种事情?
CSRF的攻击之所以能够成立,需要具备以下几个关键因素:
- 网站使用Cookie验证用户
- 用户没有登出网站
- 网站没有做任何CSRF的防御
如果网站是使用Cookie在对用户进行验证的话,会产生一个问题。那就是只要该请求是发往该网站的,无论是从哪里发出的,浏览器都会自动捎带目标网站的Cookie。因此当攻击者伪造了用户请求发向网银系统时,该伪造的请求也会携带着合法的Cookies信息。只要用户不主动登出网站,用户浏览器里的Cookies都会一直保存着登录信息,并被攻击者利用。
虽然攻击者并没有办法查看用户Cookie里的信息,但是只要能把Cookies发出去就行了呀!
该如何防御CSRF?
归根结底,CSRF之所以能成功,是因为服务器没有办法分辨哪个请求是合法的,而哪个请求又是攻击者发出的。因为他们长得都一样。
那么我们是否可以想一个办法,让合法的请求带有一些特殊的信息,而且这特殊的信息是攻击者所无法获得的呢?
其中一个办法就是加入 CSRF Token。
我们可以让服务端针对用户生成一个独一无二的token,并将CSRF token放在我们自己的合法网站中。如果用户是在我们的合法网站里发送请求的话,就会夹带CSRF token一起发出。接着,我们只需要在服务端验证一下这个CSRF token就可以知道该请求是否合法了。
而攻击者是无法获取这个CSRF token的,因为首先攻击者必须以用户的身份登录我们的合法网站才有办法获取该CSRF token,这就要求攻击者必须获得用户的账号密码,或者是从中间截取用户的合法请求,然而如此一来,这就变成了盗取账号密码以及中间人攻击了,已经与CSRF无关了。CSRF的防御只是仅仅针对CSRF攻击本身而已。
举个例子,我们可以在提交列表中加入一个隐形的input,value就设定成我们的token,当用户提交请求的时候,就会连这个CSRF token一起提交出去了。
<form action="https://bank.example.com/transfer" method="post">
<input
name="amount"
value="100"
/>
<input
type="hidden"
name="token"
value="{csrf_token}"
/>
<input type="submit"/>
</form>