什么是 CSRF 攻击?
CSRF 是指跨站的请求伪造,这种攻击方法会强迫使用者在他们已经验证身份的网站中,执行某些恶意的伪造操作,因为已经验证过该使用者,所以网站就会以操作来自该使用者,因此接受了该操作。举例来说,某个使用者登录银行帐户后,去逛别的网站,但不小心点开恶意网站,该恶意网站中的代码用这名使用者的名义,进行未经同意的转帐操作。
CSRF 之所以成立,是因为使用者的身份已经先被验证过。如果要白话一点理解,就像是别人偷拿你的会员点数卡去买东西。但因为店家认卡,所以当看到小偷拿着你的卡,就相信小偷是你本人,于是接受该小偷使用你的点数进行消费。
CSRF 攻击流程
Step1: 使用者成功登录 A 银行网站的帐户,并且代表使用者身份的 cookie 在本地保存下来,所以下次再来 A 银行网站时,不用重新登录
Step2: 由于使用者没有登出 A 银行网站的帐户,在浏览 B 恶意网站时,B 网站有个被设为透明的图片,因为是透明的,所以使用者在画面上看不到,然而该图片包含一段恶意代码,代码如下。
// 备注:CSRF 攻击手法有很多,此代码例子是一种最基本的攻击方法之一
<img
src="http://a-bank.com/transfer.do?acct=BadGuy&amount=100000 HTTP/1.1"
width="0"
height="0"
/>
Step3: 使用者虽然将看不到此图片,但是,浏览器仍会向 http://a-bank.com/ 提交请求,同时此请求是带有使用者的 cookie,所以 A 银行可以辨识使用者身份,此恶意攻击执行成功。
CSRF 防御方法
1. 加上验证
在针对比较危险的操作,可以再增加一些验证,像是图形验证码、简讯验证码等。
2. 不要用 GET 请求来做关键操作
上面通过把请求带到图片的例子,是很基本且常见的攻击方式。这种攻击之所以能很轻松做到,是因为使用了 GET 请求进行操作。因此如果要避免 CSRF,最基本地做法,是不要用 GET 请求来做关键操作,建议用 POST 请求。
当然这不是说用 POST 请求就绝对安全,只是若使用 POST 请求,会需要有使用者的提交动作才能触发。
许多钓鱼网站,都会引诱使用者去点击某些按钮,正是因为要有点击这个提交动作,才能触发 CSRF 攻击。虽然仍有可能被攻击到,但起码不会像用 GET 请求,使用者可能在完全不知情下,进到网站就马上被攻击。
3. 检查 Referrer
要避免跨站伪造请求,可以辨识请求来自哪个网站。除了使用 cookie 等验证机制,如果能够辨别出请求不是来自原本的网站,也能过滤掉伪造请求。在 HTTP 的标头中有 Referrer 字段,我们可以检查这个字段,来确保请求不是来自其他网站。
然而,这做法也不是完全无问题,因为这个字段的值是由浏览器提供的。如果某个浏览器的安全性不足,让骇客能篡改 Referrer 值,那仍有可能被攻击成功。因此我们需要更安全的防御方法。
4. CSRF token
用于认证身份的 cookie 会在每次发送请求时被浏览器自动夹带,这让恶意网站也能拿到 cookie 来做伪造请求。为了避免这种情况,我们可以使用 CSRF token。CSRF token 的做法是让网站用其他方式验证使用者身份,而不是通过 cookie。
CSRF token 需要由服务器端生成,可以为每个请求或每个 session 生成一次,但每个请求都生成会更安全。接着,这个 token 会被传送到客户端,客户端可以在表单中用隐藏字段储存起来,当客户端发出请求时,一起提交回服务器端,或作为 header 的一部分传回服务器端。服务器端需要在该用户的请求记录或 session 中找到该 token,如果客户端提供的 token 不一致,则拒绝请求。
<form action="/transfer.do" method="post">
<input type="hidden" name="CSRFToken" value="123token123">
[...]
</form>
5. 浏览器本身防护 - SameSite cookies
由于许多 CSRF 攻击是因为 cookie 被恶意网站使用,伪造请求。为了避免这种情况,可以限定 cookie 只能被自己的网站使用。我们可以通过 SameSite cookies 来实现。
Google 在 Chrome 51 版之后加入的 SameSite Cookie 功能,只要在设置 Cookie 的方式后面加上 SameSite 即可。
SameSite cookies 是 HTTP 回应标头中的 Set-Cookie 的属性之一,此属性的可能值为 Lax、Strict 或 None。Lax 或 Strict 值可以阻挡第三方网站携带 cookie 来防御 CSRF 攻击。最安全的做法是设置 Strict,Strict 会限制其他 Domain 来的任何请求都不带上 Cookie,这样一来 cookie 就只能自己的网站中使用,也就不用担心被伪造请求。而 Lax 则是限制 POST、 DELETE、 PUT 都不带上 Cookie, GET 则会带上 Cookie。
Set-Cookie: JSESSIONID=xxxxx; SameSite=Strict
Set-Cookie: JSESSIONID=xxxxx; SameSite=Lax