CSRF 攻击 及其 Laravel 解决
Laravel 可以轻松使地保护你的应用程序免受 跨站请求伪造 (CSRF) 攻击。 跨站点请求伪造是一种恶意攻击,它凭借已通过身份验证的用户身份来运行未经过授权的命令。
Laravel 会自动为每个活跃的用户的会话生成一个 CSRF「令牌」。该令牌用于验证经过身份验证的用户是否是向应用程序发出请求的用户。
简而言之,一般的 web app 会在请求的时候在 cookies
和 session
中表示出此次请求的会话 id, 每次请求的时候客户端都会向服务端发送此会话id来标示此次会话。但是 sessionID
仅能标示出此次回话的存在,只靠 sessionID
并不能识别出此次的请求是不是真的出自于该客户端用户的操作,此就造成了 CSRF 攻击的风险。
一般解决思路
我们的 web 请求的时候,需要向服务端证实本次请求确实是来自于用户,具体的解决思路如下。在服务端返回给客户端页面的时候,生成一个完全随机的 CSRF_TOKEN
,同时存储于服务端和客户端。在客户端对服务端进行请求的时候,将此 CSRF_TOKEN
发送于服务端,服务端对此 TOKEN
进行验证。如果对比一致,那么请求执行,同时 服务端和客户端刷新 CSRF_TOKEN
, 用于下一次请求。
一般 CSRF 攻击目的在于代替被攻击用户身份修改信息,所以:各种 ’写请求‘ 都需要加上一个 CSRF_TOKEN
保证安全性,而 ‘’读请求’ ‘就可以不进行 CSRF_TOKEN
验证:
/**
* Laravel 中验证逻辑,如果是 head, get, option 这三个 '读请求' 便可以跳过认证
* Determine if the HTTP request uses a ‘read’ verb.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function isReading($request)
{
return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']);
}
CSRF_TOKEN 存在于客户端的形式可以有多种
Cookies 中存储
Meta 标签中
实际上 Laravel
会同时在 cookies
和 meta
标签中存储当前会话的 CSRS_TOKEN
Cookies 的 CSRF_TOKEN
存储
Laravel 里 cookies 的 CSRF_TOKEN 存储规定于 \App\Http\Middleware\VerifyCsrfToken
这个中间件中:
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* addHttpCookies为true则会在请求返回的时候对客户端设置 CSRF_TOKEN 的 cookies
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}
如果设置了 cookies 中的 token, 那么在每一次请求的时候,都会在 request header 中附加上该信息, 但是 cookies 也有被客户端禁用的情况。
注意,laravel
中 cookies 设置的 csrf_token 并不是存在于服务端的原始值,而是经过一次加密的 csrf_token, 需要在传回服务端时进行解密比较。且由于 random byte 的存在,相同的 csrf_token 会带来不同的密文, 具体的加密解密操作可见 VerifyCsrfToken
中注入的 Encrypter
类
/* DI in VerifyCsrfToken */
public function __construct(Application $app, Encrypter $encrypter)
{
$this->app = $app;
$this->encrypter = $encrypter;
}
Meta 标签的 CSRF_TOKEN
存储
为了应对 cookies 禁用以及其他 cookies 使用不成功的时候,我们还可以在 <meta>
标签中设置此次会话的 CSRF_TOKEN
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
Laravel 获取 request中的 CSRF_TOKEN
Laravel 会从 三个渠道获得 CSRF_TOKEN
post
请求的input
输出栏 (一般为隐藏栏)request header
中的X-CSRF-TOKEN
字段如果以上两种都不存在,则在
cookies
中寻找,且进行解密操作还原为服务端的原始值
以下源码可以看出 laravel 对请求中的 csrf_token 的获取顺序:
/**
* Get the CSRF token from the request.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function getTokenFromRequest($request)
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (! $token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header, static::serialized());
}
return $token;
}
服务端比较CSRF_TOKEN
可以看到,在服务端,csrf_token 存于 $request->session()->token()
中。
laravel
中使用 hash_equals
进行两者的比较,可以使得无论两者值是什么,比较的时间都是一样从而有效的防止了 侧信道攻击。
/**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request)
{
$token = $this->getTokenFromRequest($request);
return is_string($request->session()->token()) &&
is_string($token) &&
hash_equals($request->session()->token(), $token);
}
如何传递 CSRC_TOKEN
- Method 1: 现阶段,对于每一次
post
请求最好从meta
标签中获取一个 csrf_token 作为其隐藏表单域名,name
设置为_token
。从而保证laravel
在获得 csrf_token 的时候拥有最高的效率(因为其默认先获取 input 域中的 _token)/* example for post request */ axios.post('/login', { '_token' : $('meta[name="csrf-token"]').attr('content'), 'email' : values.email, 'password' : values.password, 'remember' : values.remember, }).then((res)=>{ console.log(res); }).catch(()=>{ console.log('error'); });
- Method 2: 也可以在每一次请求的时候,给请求头赋值
X-CSRF_TOKEN
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } });
- Method 3: 当然在有 cookies 的
XSRF-TOKEN
字段情况下,可以什么都不用管直接发送请求,但这种方式较前两种最慢。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: