➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
用 React 寫 calculatorRetrofit HTTP Client

網頁安全

Table of contents

1 Cookie 同 local storage 既比較

1.1 簡單 fact sheet

  • 瀏覽器 call back-end server API 既時候會自動帶埋 cookies
  • Call back-end server API 要用到 HTTP header 既話,需要執行 JavaScript
  • HttpOnly 既 cookie 只會畀瀏覽器使用,連 JavaScript 都 access 唔到
  • 主要兩大類型既安全問題:CSRF 同 XSS 攻擊

1.2 比較

考慮/儲存方式CookieLocal storage
限制有指定 domain,做唔到 cross site必須用 JavaScript 提取
安全隱憂:CSRF有,但可防止 ①冇 ②
安全隱憂:XSS 攻擊有,但可防止 ③有 ④
解釋:
  • ①:網站可以用 SameSite cookie,咁就算用戶上錯左惡意網站,因為佢喺該網站(例如銀行)既 cookies 同惡意網站並唔係 SameSite,所以重要既 cookie 就唔會被送出;POST API 加上 CSRF token 可以防止 CSRF,因為必須要用 JavaScript 先可以將 HTTP header 既 CSRF token cookie 抄到 header。
  • ②:因為需要用 JavaScript 黎儲取 local storage,所以冇 CSRF 既隱憂。
  • ③:如果 cookie 既 HttpOnly 係 true,JavaScript 就無法讀取該 cookie;但如果係 false,亦會受到 XSS 攻擊。
  • ④:因為 XSS 攻擊就係利用惡意既 JavaScript 黎喺用戶既瀏覽器任意執行,而 local storage 係用 JavaScript 存取。
結論:我地應該用 cookie 儲存敏感既 session ID 或者 access token 資料。
註:cookie 既 SameSite 屬性同 HttpOnly 屬性需要瀏覽器支援。
參考資料:

2 Session ID 同 access token 既比較

用戶使用一般網上服務(銀行除外)好多時都會希望喺下一次訪問網頁既時候可能保持登入狀態,所以用戶一般都唔會自行登出網頁,而網頁為左做到咁,正常都會將用戶登入成功既資訊儲存喺用戶既瀏覽器。瀏覽器可以用到 cookie(一般做法)或者 local storage。用戶資訊可以係當用戶喺登入果時提供既登入帳號同密碼,但因為安全問題,將帳號同密碼儲存喺瀏覽器度(唔加密)係好危險,所以正常做法都係將一個可以對應到 back-end server session 既 session ID 儲存喺瀏覽器既 cookie(都可以用 local storage,但需要用到 JavaScript)。
Session ID 同 access token 都係為左一個共同既目的,就係喺驗證左用戶既身份之後,方便後續對 back-end server 既 API call,咁用戶使用網頁既功能既時候,front-end 網頁就唔需要再提供密碼,而係提供 session ID 或者 access token。
我地可以有以下 4 種配搭:
認證方式瀏覽器存取方式
Session IDCookie
Session IDLocal storage
Access tokenCookie
Access tokenLocal storage

2.1 Session ID 既做法

簡單咁講,Session ID 既做法就係當用戶登入成功之後,back-end server 會生成一啲用戶登入既狀態數據存放喺 back-end server,可以係 database 或者 cache。然後,back-end server 會返回一個 session ID 畀 front-end 網頁作為對應返果啲數據既一個密碼,可以係以 HTTP header Set-Cookie: "sessionId=ABCDEFG12345" 而 front-end 網頁會將呢個 session ID 記低,之後 call back-end server 既 API 去為用戶進行唔同既操作既時候就會放埋落個 API call 度一同送出。用戶成功登入之後,後續任何 call back-end server 既操作都會令 back-end server 驗證 API call 裡面既 session ID,睇下係邊個用戶操作緊、用戶有咩權限、用戶既帳號仲係咪正常既狀態。
一般黎講,啲網頁考慮到下次用戶重新開啟網頁既時候都可以 keep 返用戶成功登入左既狀態,就會喺佢由 back-end server 收到呢個 session ID 值之後將佢儲低喺瀏覽器既 cookies 或者 local storage,咁下次重新開返個網頁出黎果陣,就可以喺 cookies 或者 local storage 讀取返用戶既 session ID 值。

2.2 Access token 既做法

Access token 既做法係當用戶登入成功之後,back-end server 會生成一條好長既 token 值畀 front-end 網頁,但唔會將用戶既狀態數據存放喺 back-end server;相反,用戶既狀態數據就正正夾雜左喺個 token 裡面。
因為密碼學既關係,token 係幾乎冇可能畀人修改到而令 back-end server 信左條修改左既 token。喺生成 token 既時候,back-end server 會用到一個密碼黎將資料進行 HMAC 處理,然後將 HMAC 既值放到 token 裡面,作為簽署。
Access token 既例子有 JSON Web Token(簡稱 JWT),而應用方面就有 OAuth。
有一種做法係用 refresh token 黎 renew access token,而 refresh token 既 expiry set 好長,access token 既 expiry set 好短如 15 分鐘。

2.2.1 伏位

現時網絡上有大量網站吹捧 JWT,我地可以從唔少既網上 sample code 睇到,一般 implement JWT 既 code,例如用 Spring framework 寫既,通常都只係提供最基本既功能,就係 generate token 同埋喺 access API 既時候 intercept request(Spring 既 filter)然後 validate 個 token。
但係如果要完善整個設計,問題就在於:
  • Access token 既 expiry?
    • 到底應該 set 幾長?15 分鐘定係長達一星期或者一個月?
    • 如果 set 得太長而遇到 access token 畀人偷左,咁只能夠改 server 既 secret 或者 private key,咁會影響所有用戶?
  • Renew 機制?
    • 注意 mobile app 既話需要長期登入,亦要考慮用戶可能會有一段時間冇開個 mobile app。
    • 每次 access API 都攞一次新既 access token?
    • 如果加入 refresh token,咁都有畀人偷左 refresh token 既風險。
  • Revoke 機制?
    • 如果用戶帳號畀 admin 修改左,例如降低權限、即時停用,唔 hit database 或者 cache 去 check 而淨係睇住個 token 上既資料係冇辦法做到即時生效,只能等個 token 過期。
    • 考慮到 token 可能會畀黑客偷左,如果要用 database 或者 cache 黎 whitelist 或者 blacklist token,或者每次 access API 都 hit database 去比較 token 既 issued-at 同 user record 既更新時間,咁會增加 latency,就變左同 session ID 冇分別。
    • 考慮到以上問題,其實 stateless access token 唔太可行,但 access token 既原意係想 stateless,因為 stateless 容易做到 scalable。
  • Call API 果陣帶埋個 token,咁個 token 應該放喺 Authorization header(Bearer)定係 cookie?
    • 如果放喺 header,就必須用到 JavaScript,個 cookie 唔可以係 HttpOnly,咁就有可能受到 XSS 攻擊。

3 要面對既安全問題

3.1 CSRF

瀏覽器係會將用戶既 cookies 夾埋落每一個 HTTP call 度送出去 back-end server。如果用戶上既一個佢自以為安全但實際上係畀黑客入侵左或者根本係黑客製造出黎既假網站,而呢個網站上既網頁 call 左一個銀行既 API,咁就有可能出事。呢個網頁可以用一個 <img> tag,例如 <img src="https://real.bank.com/sendMoney?to=hacker888&money=10000" />,咁瀏覽器可能會當左佢係一張圖片而 load 條 URL 去嘗試下載圖片,但係 load 呢條 URL 既時候其實就變相去左 call 真正既銀行既 API。
咁做可行既原因係同網站一般會用 cookies 做 authentication 有關。代表用戶已經登入左網站既 cookies 會由瀏覽器自動加入相同網站既 HTTP calls 裡面,呢個係瀏覽器正常不過既 behavior。但黑客可以喺假既網站利用呢個 behavior 去令真正既銀行既 back-end server 一樣收到 cookies(session ID 或者 access token 同樣受害),因為銀行分辨唔到個 HTTP call 黎自邊個網站,所以最後用戶既錢就會成功過左畀黑客戶口。
參考資料:

3.1.1 解決方法

  • 驗證 Origin request header,呢個 request header 只會由瀏覽器加入,就連假網站都冇辦法修改到。
  • 將 session ID 儲喺 local storage,而唔係 cookies,不過用 local storage 又會有 XSS 攻擊既問題。
  • 將 session ID 儲喺 cookies,但必須係 SameSite。
  • 加入 CSRF token。
註:cookie 既 SameSite 屬性同 HttpOnly 屬性需要瀏覽器支援。

3.2 XSS 攻擊

大部分網站為左快啲完成開發,都可能會用到一啲現成既開源 CSS/JavaScript libraries,而呢啲 libraries 背後亦有可能用左大量既其他開源 libraries。開源項目雖然話係開放源碼畀任何人都睇到,但必須要有人睇過然後發掘到個問題出黎先至有用。如果唔計唔開源既項目有低機率因為員工私自惡意修改(man-in-the-middle attack),開源項目比起唔開源既項目係比較容易畀黑客修改到,從而植入惡意代碼。
呢啲惡意代碼想做乜都得,例如:
  • 讀取用戶非 HttpOnly 既 cookies 或者 local storage,然後去 call 銀行既 API,令銀行將用戶既錢過畀黑客戶口
  • 將用戶既 cookies 或者 local storage send 去黑客既一個 back-end server 既 API,然後儲存低,作日後之用
參考資料:

3.2.1 解決方法

  • 將 session ID 儲喺 cookies,但必須係 HttpOnly,咁樣可以防止 JavaScript 讀取到,但係都要用戶既瀏覽器支援 HttpOnly 既屬性先得

4 Scalability

如果用 access token 既設計完全唔 hit database,理論上係好 scalable,因為任何一個 resource server 只需要驗證個 access token 既 signature 係咪 valid 就得。但實際上冇人會咁做,因為為左完善既權限機制,最終都係要 hit 到 database。比較好既做法可以係存取一個 distributed cache,例如 Redis,咁一樣可以做到 scalable,而且係 stateless。不過同樣地,session ID 既方法都可以用 distributed cache,所以其實兩者喺 scalability 上都冇乜分別。

5 EU 法律問題:通知用戶

大家以為用左 cookie 先至需要彈 banner 通知用戶,但其實 EU 法律(如 GDPR)上,只要個網站有追蹤用戶,無論係用 cookie 定係 local storage 黎儲存 session ID 定係 access token,都一樣要出 banner 通知用戶。

6 Access token 可唔可以取代 session ID?

唔可以,但 access token 都有佢既特別 use cases,例如:
  • 下載文件
  • 出現喺 email 既 URL(query string ?token=xxx),配合短至十幾分鐘至半小時既 expiry。
參考資料: