Yii中的csrf

什么是CSRF攻擊

百度百科

跨站請(qǐng)求偽造(英語(yǔ):Cross-site request forgery)燕锥,也被稱為 one-click attack 或者 session riding桑腮,通常縮寫為 CSRF 或者 XSRF狸演, 是一種挾制用戶在當(dāng)前已登錄的Web應(yīng)用程序上執(zhí)行非本意的操作的攻擊方法匹涮。跟跨網(wǎng)站腳本(XSS)相比贴硫,XSS 利用的是用戶對(duì)指定網(wǎng)站的信任炮障,CSRF 利用的是網(wǎng)站對(duì)用戶網(wǎng)頁(yè)瀏覽器的信任目派。

防御措施

檢查Referer字段

HTTP頭中有一個(gè)Referer字段,這個(gè)字段用以標(biāo)明請(qǐng)求來(lái)源于哪個(gè)地址胁赢。在處理敏感數(shù)據(jù)請(qǐng)求時(shí)企蹭,通常來(lái)說,Referer字段應(yīng)和請(qǐng)求的地址位于同一域名下智末。以上文銀行操作為例谅摄,Referer字段地址通常應(yīng)該是轉(zhuǎn)賬按鈕所在的網(wǎng)頁(yè)地址,應(yīng)該也位于www.examplebank.com之下系馆。而如果是CSRF攻擊傳來(lái)的請(qǐng)求送漠,Referer字段會(huì)是包含惡意網(wǎng)址的地址,不會(huì)位于www.examplebank.com之下由蘑,這時(shí)候服務(wù)器就能識(shí)別出惡意的訪問闽寡。
這種辦法簡(jiǎn)單易行,工作量低尼酿,僅需要在關(guān)鍵訪問處增加一步校驗(yàn)爷狈。但這種辦法也有其局限性,因其完全依賴瀏覽器發(fā)送正確的Referer字段裳擎。雖然http協(xié)議對(duì)此字段的內(nèi)容有明確的規(guī)定涎永,但并無(wú)法保證來(lái)訪的瀏覽器的具體實(shí)現(xiàn),亦無(wú)法保證瀏覽器沒有安全漏洞影響到此字段鹿响。并且也存在攻擊者攻擊某些瀏覽器羡微,篡改其Referer字段的可能。

添加校驗(yàn)token

由于CSRF的本質(zhì)在于攻擊者欺騙用戶去訪問自己設(shè)置的地址抢野,所以如果要求在訪問敏感數(shù)據(jù)請(qǐng)求時(shí)拷淘,要求用戶瀏覽器提供不保存在cookie中,并且攻擊者無(wú)法偽造的數(shù)據(jù)作為校驗(yàn)指孤,那么攻擊者就無(wú)法再運(yùn)行CSRF攻擊启涯。這種數(shù)據(jù)通常是窗體中的一個(gè)數(shù)據(jù)項(xiàng)。服務(wù)器將其生成并附加在窗體中恃轩,其內(nèi)容是一個(gè)偽隨機(jī)數(shù)结洼。當(dāng)客戶端通過窗體提交請(qǐng)求時(shí),這個(gè)偽隨機(jī)數(shù)也一并提交上去以供校驗(yàn)叉跛。正常的訪問時(shí)松忍,客戶端瀏覽器能夠正確得到并傳回這個(gè)偽隨機(jī)數(shù),而通過CSRF傳來(lái)的欺騙性攻擊中筷厘,攻擊者無(wú)從事先得知這個(gè)偽隨機(jī)數(shù)的值鸣峭,服務(wù)端就會(huì)因?yàn)樾r?yàn)token的值為空或者錯(cuò)誤宏所,拒絕這個(gè)可疑請(qǐng)求。

Yii2中csrf的實(shí)現(xiàn)原理

Yii2是使用token來(lái)預(yù)防csrf攻擊的摊溶。

在配置文件中開啟

'request' => [
            'enableCsrfValidation' => true,
        ],

校驗(yàn)token

在所有控制器繼承的controller基類中爬骤,有個(gè)請(qǐng)求回調(diào)函數(shù)在請(qǐng)求處理前驗(yàn)證token。
在下面代碼中莫换,先判斷是否開啟了csrf驗(yàn)證霞玄,在判斷目前異常處理器是否捕獲了異常,最后才校驗(yàn)token

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;
}

接下來(lái)看看validateCsrfToken函數(shù)

public function validateCsrfToken($clientSuppliedToken = null)
{
    // 先判斷http請(qǐng)求方法拉岁,如果是get坷剧、head、options方法則直接跳過喊暖,不認(rèn)證惫企。
    $method = $this->getMethod();
    // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
    if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
        return true;
    }

    // 獲取服務(wù)器原始的token
    $trueToken = $this->getCsrfToken();

    // 如果已經(jīng)拿到了瀏覽器帶過來(lái)的token,則直接與上面的服務(wù)器端原始的token進(jìn)行比較
    if ($clientSuppliedToken !== null) {
        return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
    }
    
    // 否則從請(qǐng)求body或者請(qǐng)求head中獲取瀏覽器端的token陵叽,再進(jìn)行比較
    return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
        || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
}

先看看服務(wù)器端原始的token是怎么來(lái)的.

從注釋中可以看到雅任,服務(wù)端生成的原始token一般是保存在session或者cookie中。然后瀏覽器會(huì)通過html表單的隱藏字段
或者h(yuǎn)ttp的header帶給服務(wù)器咨跌,然后兩者比較進(jìn)行驗(yàn)證。

/**
 * Returns the token used to perform CSRF validation.
 *
 * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
 * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
 * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
 * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
 * @return string the token used to perform CSRF validation.
 */
public function getCsrfToken($regenerate = false)
{
    // 因?yàn)檫@個(gè)函數(shù)在一次請(qǐng)求中會(huì)多次調(diào)用硼婿,所以會(huì)保存token在_csrfToken字段中
    if ($this->_csrfToken === null || $regenerate) {
        $token = $this->loadCsrfToken();
        if ($regenerate || empty($token)) {
            // 重新生成token
            $token = $this->generateCsrfToken();
        }
        // 對(duì)token進(jìn)行簡(jiǎn)單加密
        $this->_csrfToken = Yii::$app->security->maskToken($token);
    }

    return $this->_csrfToken;
}

看看loadCsrfToken函數(shù)锌半,即從cookie或者session中獲取服務(wù)端的token

/**
 * Loads the CSRF token from cookie or session.
 * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
 * does not have CSRF token.
 */
protected function loadCsrfToken()
{
    // 默認(rèn)是保存在cookie中的,比較不安全
    if ($this->enableCsrfCookie) {
        return $this->getCookies()->getValue($this->csrfParam);
    }

    return Yii::$app->getSession()->get($this->csrfParam);
}

那么token是怎么生成的呢寇漫?看下面代碼可知token就是一個(gè)隨機(jī)字符串[A-Za-z0-9_-]刊殉,生成后保存在cookie中或者session中

/**
 * 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;
}

校驗(yàn)函數(shù)則比較簡(jiǎn)單了,就是解密后進(jìn)行比較

private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
{
    if (!is_string($clientSuppliedToken)) {
        return false;
    }

    $security = Yii::$app->security;

    return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
}

最后看看加解密函數(shù)

/**
 * Masks a token to make it uncompressible.
 * Applies a random mask to the token and prepends the mask used to the result making the string always unique.
 * Used to mitigate BREACH attack by randomizing how token is outputted on each request.
 * @param string $token An unmasked token.
 * @return string A masked token.
 * @since 2.0.12
 */
public function maskToken($token)
{
    // The number of bytes in a mask is always equal to the number of bytes in a token.
    $mask = $this->generateRandomKey(StringHelper::byteLength($token));
    return StringHelper::base64UrlEncode($mask . ($mask ^ $token));
}

/**
 * Unmasks a token previously masked by `maskToken`.
 * @param string $maskedToken A masked token.
 * @return string An unmasked token, or an empty string in case of token format is invalid.
 * @since 2.0.12
 */
public function unmaskToken($maskedToken)
{
    $decoded = StringHelper::base64UrlDecode($maskedToken);
    $length = StringHelper::byteLength($decoded) / 2;
    // Check if the masked token has an even length.
    if (!is_int($length)) {
        return '';
    }

    return StringHelper::byteSubstr($decoded, $length, $length) ^ StringHelper::byteSubstr($decoded, 0, $length);
}

值得一提的是州胳,存在cookie中也是比較不安全的记焊,但是要比存在session中高效?栓撞?貌似這兩種安全性差不多

最后遍膜,查看getCsrfToken函數(shù)的調(diào)用,發(fā)現(xiàn)有多次調(diào)用瓤湘,比如登錄的時(shí)候調(diào)用并且刷新token瓢颅,生成表單的時(shí)候回調(diào)用。

總結(jié)

防止csrf攻擊的主要核心在于如何確認(rèn)該請(qǐng)求是否是自己方的代碼產(chǎn)生的弛说,而不是別人惡意代碼偽造的挽懦。
token相當(dāng)于一個(gè)身份的象征,一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)token木人,從而確保請(qǐng)求的身份的認(rèn)定信柿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冀偶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子渔嚷,更是在濱河造成了極大的恐慌进鸠,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件圃伶,死亡現(xiàn)場(chǎng)離奇詭異堤如,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)窒朋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門搀罢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人侥猩,你說我怎么就攤上這事榔至。” “怎么了欺劳?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵唧取,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我划提,道長(zhǎng)枫弟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任鹏往,我火速辦了婚禮淡诗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伊履。我一直安慰自己韩容,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布唐瀑。 她就那樣靜靜地躺著群凶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哄辣。 梳的紋絲不亂的頭發(fā)上请梢,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音力穗,去河邊找鬼溢陪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睛廊,可吹牛的內(nèi)容都是我干的形真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咆霜!你這毒婦竟也來(lái)了邓馒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蛾坯,失蹤者是張志新(化名)和其女友劉穎光酣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體脉课,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡救军,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倘零。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唱遭。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呈驶,靈堂內(nèi)的尸體忽然破棺而出拷泽,到底是詐尸還是另有隱情,我是刑警寧澤袖瞻,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布司致,位于F島的核電站,受9級(jí)特大地震影響聋迎,放射性物質(zhì)發(fā)生泄漏脂矫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一霉晕、第九天 我趴在偏房一處隱蔽的房頂上張望羹唠。 院中可真熱鬧,春花似錦娄昆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至谷浅,卻和暖如春扒俯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背一疯。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工撼玄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人墩邀。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓掌猛,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親眉睹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荔茬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容