csrf是什么?

csrf全称:跨站点请求伪装(cross site request forgery)。第三方站点利用正常站点的登录凭证绕过后端身份验证,达到攻击该站点的目的。

为什么会产生csrf攻击呢?

说这个之前,我们得先知道另外一个东西,cookie, 它一般是用于浏览器存储用户的登录凭证信息。当有数据需要发送给相关站点时,会带上浏览器中该站点相关cookie信息,以达到身份校验的目的。
csrf.png

我们需要知道的几点:

1.csrf一般发生在跨站请求上,因为第三方站点更容易被“操作”。

2.cookie信息不能盗取,只能“使用”

常用的csrf防御手段

1.csrf token

这种方式是比较常见的一种

做法:

在提交内容或请求头中隐藏一个token值,token值是一次性的,确保其不可预测性,然后在服务端校验该token值。

缺点:

对于前后端分离的站点,token值不能直接渲染到前端页面上。

2.图片验证码

3.手机验证码

这些防御措施都是用来增加破解的难度,提升系统的安全性。

Yii2.0 中的csrf token实现方案

1.页面渲染

<input name="_csrf" type="hidden" id="_csrf" value="<?= Yii::$app->request->csrfToken ?>">

2.生成token

protected function generateCsrfToken()
{
  $token = Yii::$app->getSecurity()->generateRandomString();
  if ($this->enableCsrfCookie) {
    $config = $this->csrfCookie;
    $config['name'] = $this->csrfParam;
    $config['value'] = $token;
    Yii::$app->getResponse()->getCookies()->add(new Cookie($config));
  } else {
    Yii::$app->getSession()->set($this->csrfParam, $token);
  }
  return $token;
}

public function generateRandomString($length = 32)
{
    $bytes = $this->generateRandomKey($length);
  // '=' character(s) returned by base64_encode() are always discarded because
  // they are guaranteed to be after position $length in the base64_encode() output.
  return strtr(substr(base64_encode($bytes), 0, $length), '+/', '_-');
}

1.服务端线生成一个随机字符串,长度为32

2.对1)中字符串进行base64编码、然后截取前32位字符串,并把‘+’ 、‘/’ 进行替换

3.将2)中字符串保存到session中(这里假设服务端用的是session存储token值)

上面3步生成了服务端使用的token值,返回客户端时,还需要做一些运算

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, self::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;
}

这一步主要是将服务端session中使用的token,做了一次加密处理,不直接对客户端暴露真实token。

处理如下:

1.生成8位长度的随机掩码mask

2.将token与mask做一个异或运算后,并将掩码拼接在字符串最前端

3.对2)中生成的字符串进行base64编码,并把 ‘+’ 替换成 ‘.’

由此,生成了页面上的csrf token。

那接下来服务端是怎么校验这个csrf token呢?

3.校验csrf token

private function validateCsrfTokenInternal($token, $trueToken)
{
  $token = base64_decode(str_replace('.', '+', $token));
  $n = StringHelper::byteLength($token);
  if ($n <= self::CSRF_MASK_LENGTH) {
    return false;
  }
  $mask = StringHelper::byteSubstr($token, 0, self::CSRF_MASK_LENGTH);
  $token = StringHelper::byteSubstr($token, self::CSRF_MASK_LENGTH, $n - self::CSRF_MASK_LENGTH);
  $token = $this->xorTokens($mask, $token);
  return $token === $trueToken;
}

1.获取客户端csrf token值,将 ‘.’ 替换为 ‘+’,然后做base64解码

2.取出前8位字符串,这个字符串就是对应的掩码mask

3.从第9位直到最后就是做过异或运算之后的结果

4.将3)中字符串与mask做一次异或运算,此时得到的就是解密后的token值

5.将该token值与session中token进行对比,若一致,则csrf token验证通过,反之,则验证失败

4.小结

Yii2.0中csrf token生成规则 是生成随机字符串token,然后与掩码异或运算并拼接,再做base64编码。

解码规则就是对生成做一个逆运算,这里比较巧妙的地方是与掩码做异或运算,异或运算是可以逆运算的。


Summer
1 声望0 粉丝

Coding and Recording