本文主要分成三個部分,首先簡單介紹csrf,接著對照原始碼重點分析一下yii框架的驗證原理,最後針對頁面快取所導致的token被快取提出可行的方案。涉及的知識點會作為附錄附於文末。有興趣的朋友了解一下吧。
1.CSRF描述
CSRF全稱為“Cross-Site Request Forgery”,是在使用者合法的SESSION內發起的攻擊。駭客透過在網頁中嵌入Web惡意請求程式碼,並誘使受害者訪問該頁面,當頁面被訪問後,請求在受害者不知情的情況下以受害者的合法身份發起,並執行駭客所期待的動作。以下HTML程式碼提供了一個「刪除產品」的功能:
<a href="http://www.shop.com/delProducts.php?id=100" "javascript:return confirm('Are you sure?')">Delete</a>
假設程式設計師在後台沒有對該「刪除產品」請求做對應的合法性驗證,只要使用者存取了該鏈接,相應的產品即被刪除,那麼黑客可通過欺騙受害者訪問帶有以下惡意代碼的網頁,即可在受害者不知情的情況下刪除相應的產品。
2.yii的csrf驗證原理/vendor/yiisoft/yii2/web/Request.php簡寫為Request.php
/vendor /yiisoft/yii2/web/Controller.php簡寫為Controller.php
#開啟csrf驗證
在控制器裡將enableCsrfValidation為true ,則控制器內所有操作都會開啟驗證,通常做法是將enableCsrfValidation為false,而將一些敏感操作設為true,開啟局部驗證。
public $enableCsrfValidation = false; /** * @param \yii\base\Action $action * @return bool * @desc: 局部开启csrf验证(重要的表单提交必须加入验证,加入$accessActions即可 */ public function beforeAction($action){ $currentAction = $action->id; $accessActions = ['vote','like','delete','download']; if(in_array($currentAction,$accessActions)) { $action->controller->enableCsrfValidation = true; } parent::beforeAction($action); return true; }
產生token欄位
#在Request.php
先透過安全元件Security取得一個32位元的隨機字串,並存入cookie或session,這是原生的token.
/** * Generates an unmasked random token used to perform CSRF validation. * @return string the random token for CSRF validation. */ protected function generateCsrfToken() { $token = Yii::$app->getSecurity()->generateRandomString(); if ($this->enableCsrfCookie) { $cookie = $this->createCsrfCookie($token); Yii::$app->getResponse()->getCookies()->add($cookie); } else { Yii::$app->getSession()->set($this->csrfParam, $token); } return $token; }
接著透過一系列加密替換操作,產生加密後_csrfToken,這個是傳給瀏覽器的token. 先隨機產生CSRF_MASK_LENGTH(Yii2裡預設是8位元)長度的字串mask
對mask和token進行如下運算 str_replace(' ' , '.', base64_encode($mask . $this->xorTokens($token, $mask))); $this->xorTokens($arg1,$arg2)
是先補位異或運算
/** * Returns the XOR result of two strings. * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one. * @param string $token1 * @param string $token2 * @return string the XOR result */ private function xorTokens($token1, $token2) { $n1 = StringHelper::byteLength($token1); $n2 = StringHelper::byteLength($token2); if ($n1 > $n2) { $token2 = str_pad($token2, $n1, $token2); } elseif ($n1 < $n2) { $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1); } return $token1 ^ $token2; } public function getCsrfToken($regenerate = false) { if ($this->_csrfToken === null || $regenerate) { if ($regenerate || ($token = $this->loadCsrfToken()) === null) { $token = $this->generateCsrfToken(); } // the mask doesn't need to be very random $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH); // The + sign may be decoded as blank space later, which will fail the validation $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); } return $this->_csrfToken; }
驗證token
在controller.php裡呼叫request.php裡的validateCsrfToken方法
#/** * @inheritdoc */ public function beforeAction($action) { if (parent::beforeAction($action)) { if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); } return true; } return false; } public function validateCsrfToken($token = null) { $method = $this->getMethod(); if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) { return true; } $trueToken = $this->loadCsrfToken();//如果开启了enableCsrfCookie,CsrfToken就从cookie里取,否者从session里取(更安全) if ($token !== null) { return $this->validateCsrfTokenInternal($token, $trueToken); } else { return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); } }
取得客戶端傳入
$this->getBodyParam($this->csrfParam)
然後是validateCsrfTokenInternal
private function validateCsrfTokenInternal($token, $trueToken) { if (!is_string($token)) { return false; } $token = base64_decode(str_replace('.', '+', $token)); $n = StringHelper::byteLength($token); if ($n <= static::CSRF_MASK_LENGTH) { return false; } $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH); $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH); $token = $this->xorTokens($mask, $token); return $token === $trueToken; }
加密時用的是 str_replace(' ', '.', base64_encode(mask.mask.this->xorTokens(token,token,mask)));
解密1.首先要把.替換成2.然後base64_decode 再根據長度分別取出mask和mask和this->xorTokens(token,token,mask) ; 為了說明方便this−>xorTokens(this−>xorTokens(token, $mask) 這裡稱作token1 然後進行mask和token1的異或運算,即得到token注意在加密時
token1=token^mask
所以解密時
token=mask^token1=mask^(token^mask)
3.token快取的解決方案
當頁面整體被快取後,token也被快取導致驗證失敗,一種常見的解決思路是每次提交前重新獲取token,這樣就可以通過驗證了。
附錄:
str_pad()
,該函數傳回input 被從左端、右端或同時兩端填入到製定長度後的結果。如果可選的pad_string 參數沒有被指定,input 將被空格字元填充,否則它將被pad_string 填充到指定長度;
##str_shuffle() 函數打亂一個字串,使用任何一種可能的排序方案。
因為yii2 csrf的驗證的加解密涉及到異或運算
所以需要先補充php裡字串異或運算的相關知識,不需要的可以跳過
^異或運算不一樣返回1 否者返回0 在PHP語言中,經常用來做加密的運算,解密也直接用^就行字串運算時利用字元的ascii碼轉換為2進位來運算單一字元運算
#1.對於單一字元和單一字元的直接計算其結果即可如表裡的a^b
2.對於長度一樣的多個字串如表裡的ab^cd 計算a^c對應的結果和和b^d對應的結果對應的字符連接起來#
相關教學:PHP影片教學
#以上是Yii2框架的csrf驗證原理分析及token快取解決方案的詳細內容。更多資訊請關注PHP中文網其他相關文章!

PHP仍然流行的原因是其易用性、靈活性和強大的生態系統。 1)易用性和簡單語法使其成為初學者的首選。 2)與web開發緊密結合,處理HTTP請求和數據庫交互出色。 3)龐大的生態系統提供了豐富的工具和庫。 4)活躍的社區和開源性質使其適應新需求和技術趨勢。

PHP和Python都是高層次的編程語言,廣泛應用於Web開發、數據處理和自動化任務。 1.PHP常用於構建動態網站和內容管理系統,而Python常用於構建Web框架和數據科學。 2.PHP使用echo輸出內容,Python使用print。 3.兩者都支持面向對象編程,但語法和關鍵字不同。 4.PHP支持弱類型轉換,Python則更嚴格。 5.PHP性能優化包括使用OPcache和異步編程,Python則使用cProfile和異步編程。

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP在現代化進程中仍然重要,因為它支持大量網站和應用,並通過框架適應開發需求。 1.PHP7提升了性能並引入了新功能。 2.現代框架如Laravel、Symfony和CodeIgniter簡化開發,提高代碼質量。 3.性能優化和最佳實踐進一步提升應用效率。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP類型提示提升代碼質量和可讀性。 1)標量類型提示:自PHP7.0起,允許在函數參數中指定基本數據類型,如int、float等。 2)返回類型提示:確保函數返回值類型的一致性。 3)聯合類型提示:自PHP8.0起,允許在函數參數或返回值中指定多個類型。 4)可空類型提示:允許包含null值,處理可能返回空值的函數。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3漢化版
中文版,非常好用

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

Dreamweaver CS6
視覺化網頁開發工具

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

禪工作室 13.0.1
強大的PHP整合開發環境