?譯者 | 陳峻
審校 | 孫淑娟
跨站點請求偽造(Cross-site request forgery,又稱:跨站點引用偽造)是一種針對Web應用的攻擊形式。黑客通過偽裝惡意請求,誘騙用戶運行他們本不打算執行的任務。盡管CSRF可能聽起來與XSS攻擊類似,但它們的執行方式存在根本差異。對此,Web服務器需要一種機制,來確定瀏覽器所產生的請求,是否源于合法用戶的真實意圖,而非受攻擊的脅迫。針對此類問題,服務器端可以生成一個唯一的、且不可預測的密鑰值,作為CSRF令牌被包含在客戶端的HTTP請求中。當有后續請求發出時,Web服務器會驗證包含了令牌的請求參數,以拒絕那些不包含有令牌的請求參數。由于黑客幾乎不可能構造一個完整、有效的HTTP請求來欺騙Web用戶,因此該方法通??杀挥糜诜婪禖SRF攻擊。下面,我將和您討論CSRF令牌的工作原理,及其在應用安全中的重要性。
一、為什么需要有效的CSRF令牌?
CSRF令牌通常被建議添加到所有狀態更改(state-changing)的請求中,以便在后端被執行驗證。由于只有應用服務器和客戶端可以識別令牌,因此后端必須確保傳入的請求包含有效的CSRF令牌,以避免XSS或跨站點請求偽造攻擊的得逞。
在基于Cookie的會話期間,作為密鑰值的CSRF令牌需要被安全處理以保持有效。為此,令牌應當被放置在HTML表單的隱藏字段中,被傳輸到客戶端,并使用HTTP的POST請求被提交。作為優秀的實踐,我們建議使用標準的標頭來驗證請求的來源,并使用其他措施去識別和比較來源和目標。如果來源匹配,則判定請求是合法的;如果不匹配,則表明疑似跨域請求,應予以丟棄。
二、CSRF令牌在防止攻擊中的意義
由于令牌在生成過程中使用到了偽隨機數(pseudo-random number)生成器、靜態密鑰、以及種子時間戳,因此CSRF令牌的值是不可預測的。同時,每個用戶的令牌也是不同的,而且只會存儲活動的用戶會話。據此,安全團隊可以通過將隨機數生成器的輸出,與用戶特定的熵(entropy)連接起來,并對整個結構進行散列處理,以提高令牌值的唯一性。對此,黑客將很難在早期的會話Cookie中,根據已發布的令牌樣本,去猜測CSRF令牌。
三、如何使用CSRF令牌
盡管我們可以在URL查詢字符串中放置令牌,但是查詢的字符串記錄會被留存在服務器和客戶端的多條記錄中。因此,查詢字符串可以在客戶端屏幕的瀏覽器上被訪問到,甚至會在HTTP引用標頭中,被傳輸給第三方應用程序??梢?,這種方法是不安全的。對此,我們建議CSRF令牌應該被存儲在服務器端的應用程序中,并在自定義請求標頭中傳輸CSRF令牌。服務器端應用程序通過按需驗證每個請求,以確保各種有效的請求,包含了與用戶活動會話中存儲的值相匹配的令牌。同時,CSRF令牌也可以對包括POST、PUT和DELETE在內的所有HTTP方法執行驗證。
四、如何在Java中實現CSRF令牌
由于Java應用缺乏了針對CSRF攻擊的固有保護。因此,我們建議在Java中實現CSRF令牌的時候,請使用一個過濾器和一些輔助類,來啟用令牌的創建、資源的分析、以及響應的構建。例如,您可以使用通用無狀態(Generic Stateless)過濾器,來實現了雙重提交(double-submit)的Cookie模式,以啟用CSRF保護。同時,您可以采用如下工作流程:首先,如下面代碼段所示,過濾器會在Java應用的web.xml文件中被定義:
<filter>
<filter-name>CSRFFilter</filter-name>
<filter-class>com.github.adriancitu.csrf.GenericCSRFStatelessFilter</filter-class>
<filter>
<filter-mapping>
<filter-name>CSRFFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
該過濾器包括了兩個可選的初始化變量:
1.csrfHeadername– 包含了令牌的標頭名稱
2.csrfCookieName– 用于存儲CSRF令牌的Cookie的ID。
對于每個HTTP請求,過濾器都會提供一個ExecutionContext類的實例。它包含了CSRFcookie、以及HTTP請求與響應的Java對象。同時,該類也包含了ResourceCheckerHook、ResponseBuilderHook和TokenBuilderHook等輔助類的實現。
接著,過濾器會根據如下三種情況,去檢查所請求的HTTP資源的保護狀態:
1.MUST_NOT_BE_PROTECTED
2.MUST_BE_PROTECTED_BUT_NO_COOKIE_ATTACHED
3.MUST_BE_PROTECTED_AND_COOKIE_ATTACHED
對于狀態為MUST_NOT_BE_PROTECTED和MUST_BE_PROTECTED_BUT_NO_COOKIE_ATTACHED的資源,過濾器會生成一個由TokenBuilderHook類提供的CSRF令牌的Cookie。對于帶有標簽MUST_BE_PROTECTED_AND_COOKIE_ATTACHED的資源,過濾器則使用ResourceCheckerHook去檢查資源的CSRF保護狀態,然后使用類ResponseBuilderHook向客戶端返回一個響應。當然,上述代碼只是一個參考示例,在實際運用中,還需要開發團隊在源代碼中進一步構建CSRF緩解機制。
五、如何在PHP中實現CSRF令牌
由于PHP使得開發人員能夠創建具有交互功能的動態網站,因此它在內容管理系統領域備受歡迎。不過,我們需要在PHP聯系人和用戶輸入的表單中,通過實現post處理程序,對傳入請求予以驗證,讓網站免受CSRF的攻擊。在PHP聯系人表單中,實現CSRF保護的典型工作流程如下:首先,我們需要在登錄頁面上創建一個表單的footer腳本。該腳本會調用SecurityService(一個PHP類),生成令牌,并啟動PHP會話。SecurityService會被寫入這個用于驗證請求的令牌,并將令牌加載到隱藏字段中。如下代碼展示了典型的SecurityService.php配置:
public function getCSRFToken()
{
if (empty($this->session[$this->sessionTokenLabel])) {
$this->session[$this->sessionTokenLabel] = bin2hex(openssl_random_pseudo_bytes(32));
}
if ($this->hmac_ip !== false) {
$token = $this->hMacWithIp($this->session[$this->sessionTokenLabel]);
} else {
$token = $this->session[$this->sessionTokenLabel];
}
return $token;
}
左右滑動查看完整代碼然后,頁面上呈現PHP聯系人表單,以便用戶輸入諸如主題、消息、姓名和電子郵件等詳細信息。表單還應當在隱藏字段中包含已生成的標記--csrf-token。在用戶單擊提交按鈕后,應用程序將執行JQuery表單驗證,并將各種參數發布到PHP上。
此外,在聯系人表單被提交后,該表單會執行一個腳本,將已嵌入的令牌與會話中存儲的令牌進行比較。如果令牌匹配,應用程序會去響應用戶的請求。如果不匹配,PHP將通過錯誤消息去告知用戶。
六、如何在Django中實現CSRF令牌
Django提供了一個開箱即用的CSRF中間件標簽,您可以輕松啟用它,以抵御CSRF攻擊。如下工作流將向您展示如何在已有的框架內,實現針對CSRF的防護:在Django中,CSRF中間件是被默認啟用的。如果開發人員想覆蓋此設置,則應該在任何視圖之前事先聲明django.middleware.csrf.CsrfViewMiddleware,以啟用CSRF令牌驗證。對于一些特定視圖,開發人員可以調用csrf-protect裝飾器(decorator)。此類裝飾器可用于在輸出中插入CSRF令牌的視圖。如下代碼段展示了裝飾器的基本配置:
from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
c = {}
# ...
return render(request, "a_template.html", c)
左右滑動查看完整代碼開發人員可以通過在<form>元素中包含csrf_token標簽,來啟用對使用POST表單的模板的保護。當然,這僅適用于那些具有內部URL的表單,并不適用于針對外部URL的POST表單。對于外部URL,我們可以使用如下方法,來調用CSRF令牌的標記:
<form method="post">{% csrf_token %}
此外,我們還建議使用RequestContext,在各種相應的Django視圖中呈現響應。
七、如何在Javascript中實現CSRF令牌
所有Javascript框架都帶有實現CSRF防護的默認選項。例如,Express.js就包含了csurf中間件,可協助創建和驗證各種令牌。請使用如下流程來啟用csurf,并達到CSRF的防護效果:首先,請在index.js文件中包含以下代碼:
app.get('/', csrfProtection, (req, res) => {
res.render('index', { csrfToken: req.csrfToken() });
左右滑動查看完整代碼接著,請使用類似如下代碼的配置,將index.ejs添加到各個視圖文件夾中:
<input type='hidden' name='_csrf' value='<%= csrfToken %>'>
<label for='name'> Name:</label>
<input type='text' name='name'>
<button type='submit'> Update </button>
</form>
左右滑動查看完整代碼主配置文件中的“/”路由會在index.ejs模板中呈現csrfToken變量,并在隱藏字段中插入令牌。當用戶提交表單時,對應的請求會被添加到“/profile” 路由中,以供CSRF令牌驗證。如果缺少此CSRF令牌,應用程序將返回一個無效的CSRF令牌錯誤。
八、如何修復無效的CSRF令牌
正如前文所述,CSRF攻擊將導致用戶會話被未經驗證的訪問,并產生嚴重的后果。為了防范此類攻擊,確保用戶使用有效令牌來發布請求,我們需要通過如下方法來及時修復和防止無效令牌:
- 使用自定義的請求標頭
在易受攻擊的應用程序中,添加CSRF令牌往往需要更改用戶界面等復雜的管理任務。而作為替代方案,安全團隊可以構建自定義的請求標頭,以使用同源策略(same-origin policy)來加強CSRF防御。安全策略通過強制限制自定義的標頭只能在Javascript中被構建,并且只能在其源頭中被使用的方式,來拒絕各種跨域請求。
- 利用內置和現有的CSRF處置措施
在大多數情況下,許多開發框架都包含了,內置于安全套件中的同步器令牌的防御機制。它們可以有效地保護整個應用程序的技術棧。如果在開發團隊現有的技術棧中,所用到的框架已經內置提供了默認的抵御CSRF的選項,那么您完全可以嘗試著在此基礎上,根據業務用例的實際,進行自定義和配置實施。
- 部署基于UI的CSRF防御
CAPTCHA、重新驗證(re-authentication)的認證機制、以及一次性令牌之類的機制,都可以通過分析請求,來防范那些通過CSRF進行未經授權的操作,并過濾掉各種非法操作請求。它們雖然提供了強大的驗證與防御措施,但是我們需要通過部署UI來改變用戶的體驗,以便更好地處理關鍵性的安全操作。
原文鏈接:https://dzone.com/articles/what-is-a-csrf-token
譯者介紹
陳峻 (Julian Chen),51CTO社區編輯,具有十多年的IT項目實施經驗,善于對內外部資源與風險實施管控,專注傳播網絡與信息安全知識與經驗;持續以博文、專題和譯文等形式,分享前沿技術與新知;經常以線上、線下等方式,開展信息安全類培訓與授課。?