概述
关于如何启用 CSRF 防护,Codeigniter(CI) 官方文档有详细说明。
CSRF 的防御一般比较通用的方案有:
- Referer验证
- Token验证
- 验证码防御
Referer 验证用起来比较简单,但由于各浏览器在 Referer 字段上有着不同的标准,再加上部分验证代码存在逻辑漏洞,所以,一直以来这个方案存在被绕过的风险,效果不是特别理想。而验证码防御的方案又对用户体验有比较大的影响,所以目前来说,基于 token 的验证是最为可用的 CSRF 防护方案,大量开源框架都采用这种方式来抵御 CSRF 攻击的。当然,也有组合这几种的防御方案,效果比单一使用某一方案更优。
CI & CSRF
CI 默认并未开启 CSRF 防御
|
|
若将该值设置为TRUE
,则 CI 会对每个 POST 请求进行 CSRF Verify
|
|
上面这一步是在 Input 类的 construct 函数里进行的,这意味着一旦开启 CSRF 防御,将全局作用于所有请求,这样不得不为每个 POST 表单添加一个 token 字段,否则请求会被中断。使用 CI 内置的 form_open()
函数将会自动地在表单中插入一个隐藏的 CSRF 字段,但是如果用 Ajax 去提交 POST 请求,就得为每个请求手动添加 csrf_cookie_name 字段,随请求一起传给服务端。
|
|
csrf_verify()
函数对非 POST 请求直接写 CSRF cookie,POST 请求就判断是否在 URI 的白名单里面,若不在白名单再进行判断 _POST 和 _COOKIE 里是否有 CSRF token,并且判断两者是否相等,只有在存在且相等的情况下才通过校验,否则提示错误403。这一步的逻辑其实是建立在发起 CSRF 攻击的黑客理论上无法获取第三方 cookie 基础上的,得不到 cookie 便无法构造表单里的 token,就过不了这里的 csrf_verify()
。
在校验完成后,为了不污染 _POST 数组,CI 调用 unset 函数处理了 _POST 数组里的 csrf token,重新生成新的 token 并写到 cookie。
|
|
上面是设置 CSRF 防御 cookie 的函数,cookie 属性可以从配置文件里读取
|
|
cookie 有效期默认是7200秒,也就是2小时,由于一旦开启 CSRF 防御,就是全局作用于所有的请求,所以有时候难免会有误伤,但 CI 并没做到在 controller 层动态配置,而是在 Input 类初始化的位置调用 csrf_verify()
函数完成校验。当然,CI 开放了白名单策略,也就是配置文件里的 $config['csrf_exclude_uris']
,可以把不需要做 CSRF 防御的 uri 添加到该值里面。
总结
从源码里不难看出 CI 通过比较 _COOKIE 和 _POST 中的 token 值来做 CSRF 防御,咋一看似乎觉得 _COOKIE 和 _POST 的内容对客户端来说都是可控的,但仔细思考一下,在 CSRF 的攻击场景下,攻击者是无法读取到受害者的 cookie 信息的,这也是该防御方式有效的前提所在。