Compromising Authentication#

概述#

大多數網站都提供登入功能,這是 authentication(身份驗證)的一種形式 — 在使用者回到網站時識別其身份。身份驗證讓使用者能在線上社群中擁有身份、發布內容、傳送訊息、進行購物等。

然而,取得使用者帳號的存取權對駭客來說極具誘惑力。在網路時代,駭客可以輕易地:

  • 在暗網上販售被盜的憑證
  • 劫持社群媒體帳號散播垃圾資訊
  • 進行金融詐騙

本章探討駭客如何在登入和身份驗證過程中入侵使用者帳號,以及如何防禦這些攻擊。

身份驗證的實作方式#

HTTP-Native Authentication#

Authentication 是 HTTP 協定的一部分。伺服器回傳 401 狀態碼並加上 WWW-Authenticate header,瀏覽器就會向使用者請求帳號密碼。

兩種常見的 HTTP 驗證方式:

  • Basic authentication:瀏覽器將 username:password 以 Base64 編碼後放入 Authorization header
  • Digest authentication:瀏覽器根據使用者名稱、密碼和 URL 產生 hash(雜湊值)發送給伺服器

雖然身份驗證內建於 HTTP 協定中,但主流網站很少使用原生的驗證方式。原因在於:瀏覽器的驗證提示框無法客製化樣式、無法自動填入、也沒有重設密碼的機制,使用者體驗不佳。

Figure 9-1: Google Chrome 原生的 HTTP 登入提示視窗

Non-Native Authentication#

現代網站通常使用自訂的 HTML 登入表單:

<form action="/login" method="post">
  <input type="email" name="username" placeholder="Type your email" />
  <input type="password" name="password" placeholder="Type your password" />
  <input type="submit" name="login" value="Log in" />
</form>
  • 使用者名稱以 <input type="text"><input type="email"> 輸入
  • 密碼以 <input type="password"> 輸入(以 * 遮蔽字元)
  • 帳號密碼透過 POST request 發送給伺服器
  • 登入失敗回傳 401,成功則重導向到首頁

Figure 9-3: 使用者熟悉的非原生登入表單

Brute-Force Attacks#

Brute-force attacks(暴力破解攻擊)是指攻擊者使用腳本,對登入頁面嘗試數千個常用密碼。由於過去的資料外洩已經洩漏了數百萬個常用密碼,攻擊者很容易知道該先嘗試哪些密碼。

最常見的密碼清單(由 SplashData 提供):

  1. 123456
  2. password
  3. 12345678
  4. qwerty
  5. 12345

如果你的使用者使用這些常見密碼,brute-force 攻擊很容易成功。

緩解措施 1:使用第三方身份驗證#

最安全的身份驗證系統是你不需要自己撰寫的系統。考慮使用第三方服務(如 Facebook Login)讓使用者透過社群媒體憑證登入,這對使用者方便,也免除了你儲存密碼的負擔。

大型科技公司提供的身份驗證服務大多基於 OAuthOpenID 標準:

  • Email 相關服務 → Google OAuth
  • 技術服務 → GitHub OAuth
  • 其他選擇 → Twitter、Microsoft、LinkedIn、Reddit、Tumblr 等

你可以混合搭配多種身份驗證系統,選擇適合你使用者群體的方案。

緩解措施 2:整合 Single Sign-On (SSO)#

如果你的目標客群是企業用戶,考慮整合 Single Sign-On (SSO) 身份提供者,如 Okta、OneLogin 或 Centrify。

SSO 的優點:

  • 跨企業系統集中管理身份驗證
  • 員工可以用公司信箱無縫登入第三方應用
  • 公司管理員保有對員工存取權限的最終控制權
  • 使用者憑證安全地儲存在公司伺服器上

SSO 通常使用 Security Assertion Markup Language (SAML) 標準,雖然比 OAuth 或 OpenID 較不友善,但大多數程式語言都有成熟的 SAML 函式庫。

緩解措施 3:建立安全的自建身份驗證系統#

如果需要自建身份驗證系統,有許多關鍵設計決策需要做對。

使用者名稱、Email 或兩者兼用#

  • 大多數網站要求使用者提交有效的 email 地址
  • 對許多網站而言,email 地址就是使用者名稱(因為每個 email 對應唯一的帳號)
  • 如果使用者有公開的個人檔案或需要在留言區顯示名稱,應另外要求設定 display name

使用 email 地址作為顯示名稱是不好的做法,會招來騷擾和垃圾信。

驗證 Email 地址#

驗證 email 的步驟:

  1. 格式驗證:檢查是否包含 @ 符號和有效的網域
  2. MX 記錄驗證:查詢該網域的 DNS MX 記錄,確認可以接收郵件
import dns.resolver
def email_domain_is_valid(domain):
    for _ in dns.resolver.query(domain, 'MX'):
        return True
    return False
  1. 發送驗證信:寄送包含 email verification link 的郵件,連結中含有隨機產生的 validation token,使用者點擊後確認擁有該 email

在使用者驗證 email 之前,不要假設他們擁有該 email 帳號。未經驗證就發送 transactional email 或加入 mailing list,會被 email 服務提供者列入黑名單。

禁止使用拋棄式 Email#

部分使用者會使用 10 Minute Mail、Mailinator 等拋棄式 email 服務註冊。如果需要防止此行為,可以使用維護良好的黑名單來偵測和封鎖拋棄式 email 網域。

Figure 9-2: 一次性拋棄式電子郵件服務

安全的密碼重設#

  • 發送包含新的 validation token 的 password-reset link
  • 重設連結應該是短期有效的,建議 30 分鐘後過期
  • 使用後即失效,防止攻擊者入侵 email 後利用舊的重設連結

要求複雜密碼#

  • 密碼應包含數字、符號、大小寫字母
  • 最低長度至少 8 個字元,越長越好
  • 研究顯示密碼長度比特殊字元更重要

與其強制使用者遵循嚴格的密碼規則(使用者通常只會在常用密碼後面加數字),不如使用 JavaScript 函式庫(如 password-strength-calculator)在使用者輸入時即時評估密碼強度,引導他們選擇更安全的密碼。

安全儲存密碼#

絕對不要以明文(cleartext)儲存密碼。如果資料庫被攻破,所有使用者的帳號以及他們在其他網站上使用相同密碼的帳號都會被入侵。

Hashing(雜湊)#

使用 cryptographic hash algorithm 處理密碼後再儲存。Hash 是一種單向數學函式,將輸入轉為固定長度的 bit string,在計算上無法反向推導。

推薦使用 bcrypt 演算法,它允許增加迭代次數(rounds 參數),隨著運算能力提升可以加強 hash 的強度:

import bcrypt
password = "super secret password"

# 以隨機產生的 salt 進行 hash
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=14))

# 驗證密碼
if bcrypt.checkpw(password, hashed):
    print("It matches!")
else:
    print("It does not match :(")

Salting(加鹽)#

使用者的密碼選擇往往缺乏創意。攻擊者在破解洩漏的密碼 hash 清單時,常使用 rainbow tables — 預先計算好的常用密碼 hash 對照表。

Salt(鹽值)是在 hash 過程中加入的隨機元素,使相同的密碼產生不同的 hash 值:

  • 可以在設定檔中儲存全域 salt 值
  • 更好的做法是為每個使用者產生獨立的 salt 值,並與密碼 hash 一起儲存
  • 這使得 rainbow table 攻擊變得不可行,因為攻擊者必須為每個 salt 值重新產生整張 rainbow table

要求 Multifactor Authentication (MFA)#

無論密碼儲存多安全,基於密碼的身份驗證系統始終容易受到 brute-force 攻擊。Multifactor authentication (MFA) 要求使用者至少提供以下三類資訊中的兩類:

類別說明範例
Something you know你知道的東西密碼、PIN 碼
Something you have你擁有的東西手機、實體金鑰
Something you are你本身的特徵指紋、臉部辨識

對網站而言,MFA 通常是要求使用者輸入帳號密碼後,再提供手機上 authenticator app 產生的六位數隨機碼。攻擊者必須同時取得憑證受害者的手機才能入侵帳號。

如果你的網站處理任何形式的金融交易,強烈建議實作 multifactor authentication。許多程式碼函式庫可以協助整合。

實作並保護登出功能#

  • 別忘了提供登出功能,對於共用裝置上的使用者這是重要的安全考量
  • 登出時應清除瀏覽器中的 session cookie
  • 同時在伺服器端使 session identifier 失效
  • 清除 session cookie 只需回傳包含空白 session 參數值的 Set-Cookie header

防止使用者列舉(User Enumeration)#

攻擊者常利用洩漏的憑證清單,嘗試在目標網站上確認哪些使用者名稱存在,再針對這些帳號進行密碼猜測。

防禦方式#

  • 統一錯誤訊息:無論使用者名稱不存在還是密碼錯誤,都顯示相同的通用訊息(如 An incorrect username or password was entered
  • 防止 timing attacks:即使使用者名稱無效,也要計算密碼 hash,避免攻擊者透過 response time 差異推斷帳號是否存在
  • 密碼重設頁面:不要洩漏 email 是否已註冊,使用中性訊息如 Check your inbox
  • 實作 CAPTCHA:使用 CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart)增加自動化攻擊的難度。Google 的 reCAPTCHA 是一個容易整合的選擇

CAPTCHA 並非完美解決方案,攻擊者可以使用機器學習技術或付費請人類來破解。但它們通常足以阻擋大部分自動化攻擊。

Figure 9-4: CAPTCHA 驗證碼挑戰

總結#

  • 使用第三方身份驗證(如 Facebook Login、OAuth)或 SSO 是最安全的選擇
  • 自建身份驗證系統時需要注意:
    • 使用 email 作為使用者名稱,並發送驗證信確認 email 所有權
    • 密碼重設連結應短期有效,使用後即失效
    • 使用 cryptographic hash algorithm(如 bcrypt)儲存密碼,並加入 salt
    • 考慮實作 multifactor authentication 增加安全性
    • 提供安全的登出功能,同時清除瀏覽器 cookie 和伺服器端 session
    • 保持登入錯誤訊息的通用性,防止使用者列舉攻擊