この記事は主に 3 つの部分に分かれており、最初に csrf について簡単に紹介し、次にソース コードに基づいて yii フレームワークの検証原理を分析することに焦点を当て、最後に、csrf によって引き起こされるトークン キャッシュに対する実現可能な解決策を提案します。ページのキャッシュ。関連する知識ポイントは、記事の最後に付録として添付されます。興味のある友達は調べてみましょう。
1.CSRF の説明
CSRF は「Cross-Site Request Forgery」の略で、ユーザーの正当な SESSION 内で開始される攻撃です。ハッカーは、悪意のある Web リクエスト コードを Web ページに埋め込み、被害者をそのページにアクセスするように誘い込みます。ページにアクセスすると、被害者の知らないうちに被害者の法的アイデンティティでリクエストが開始され、ハッカーの予期したアクションが実行されます。次の HTML コードは、「製品の削除」機能を提供します。
<a href="http://www.shop.com/delProducts.php?id=100" "javascript:return confirm('Are you sure?')">Delete</a>
プログラマが、バックグラウンドで「製品の削除」リクエストに対応する合法性検証を実行しないと仮定します。ユーザーがこのリンクを使用すると、該当製品が削除され、ハッカーは被害者をだまして次のような悪意のあるコードを含む Web ページにアクセスさせ、被害者の知らないうちに該当製品を削除する可能性があります。
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; }
Generate token field
Request.phpで
まずセキュリティを通じて取得します。コンポーネント セキュリティ 32 ビットのランダムな文字列で、Cookie またはセッションに保存されます。これはネイティブ トークンです。
/** * 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 が生成されます。これはブラウザに渡されるトークンです。最初に CSRF_MASK_LENGTH (Yii2 のデフォルトは 8 ビット) 長さの文字列マスクをランダムに生成します
マスクとトークンに対して次の操作を実行します。 (' ' , '.',base64_encode($mask . $this->xorTokens($token, $mask))); $this->xorTokens($arg1,$arg2) は fill-first です。 XOR 操作
/** * 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; }
検証トークン
controller.php
/** * @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)
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 を実行し、長さに応じてマスクとマスクをそれぞれ取り出します。 this- >xorTokens(token,token,mask) ; 説明の便宜上、this->xorTokens(this->xorTokens(token, $mask) を token1 と呼び、マスクと token1 の XOR 演算を実行してトークンを取得します。 token1=token^mask
Whenページ全体がキャッシュされ、トークンもキャッシュされ、検証が失敗します。一般的な解決策は、検証に合格できるように、各送信前にトークンを再取得することです。付録:
str_pad()、この関数は、入力を左端、右端、または両端から指定された長さにパディングした後の結果を返します。同時に。オプションの Pad_string パラメータが指定されていない場合、入力はスペース文字で埋められ、それ以外の場合は、指定された長さまで Pad_string で埋められます; yii2 csrf 検証の暗号化と復号化には XOR 演算 ^XOR 演算が異なる場合は 1 を返し、そうでない場合は 0 を返します。 PHP 言語では、暗号化操作によく使用され、復号化にも直接使用されます^ 文字列操作を実行する場合、文字の ASCII コードがバイナリに変換されて単一文字操作 1 が実行されます。単一文字および単一文字の場合、結果は表 a^b
2 に示すように直接計算できます。表内の ab^cd など、同じ長さの複数の文字列の場合は、 a^c に対応する結果と b^d に対応する結果に対応する文字を計算し、それらを接続します 関連チュートリアル: PHP ビデオ チュートリアル 以上がYii2フレームワークのcsrf検証原理とトークンキャッシュソリューションの分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。token=mask^token1=mask^(token^mask)
str_shuffle()
function 可能な並べ替えスキームを使用して文字列をシャッフルします。