web safe csrf
在 web 应用上存在很多安全风险,如 csrf(XSRF),既伪造用户请求向网站发起恶意请求,是一种对网站的恶意利用。
通常防御方式都是服务器分发凭证,每次提交请求或者表单时带上凭证给服务器校验是否为恶意的请求
常用的防范方案
- 对于服务端渲染的表单页面可以把 token 渲染及隐藏在特定的地方,from 表单提交的时候把该 token 通过约定的参数带给后台
- 将 token 设置在 Cookie 中,在提交 post 请求的时候提交 Cookie,并通过 header 或者 body 带上 Cookie 中的 token,服务端进行对比校验
- 将 token 存在 custom header 上,服务端通过校验请求自定义头部字段值
- 在链接 url 上带上 token 参数
Example
基于 koa 框架采用服务端 setCookie 的方式来讲解
步骤
- 服务器接受到请求,通过响应页面时将 token 渲染到页面上的 form 隐藏域中
<input type="hidden" name="_csrf" value="xxxx">
- 服务端将 token 设置 cookie 带回客户端
response headers -> Set-Cookie: xxx
- 当用户发送 GET 或者 POST 请求时带上
_csrf
参数(对于 Form 表单直接提交即可,因为会自动把当前表单内所有的 input 提交给后台,包括_csrf
) - 后台在接受到请求后解析请求的 cookie 从 session 中 获取 secret 的值,然后和用户请求提交的 _csrf 做解析比较,如果相等表示请求是合法的
所需 middleware
option
- key,cookie key,默认 koa:sess
- maxAge,存储时间,默认 1 小时
- overwrite,覆盖同名的 cookie,默认允许
- httpOnly,仅服务器可以访问 cookie,不予许客户端 js 访问,默认开启
- signed,安全性相关,koa 的 cookie 本身带了安全机制的签名
app.keys 密钥
,通过ctx.cookies.set('name', 'tobi', { signed: true })
就可以设置,对于 cookie 默认开启签名 - rolling,强制在每个响应中设置会话标识符 cookie。 过期被重置为原始的 maxAge,重置到期倒计时。默认关闭。
- renew,会话即将过期时更新会话。默认关闭
- store,外部存储
- encode,自定义编码
- decode,自定义解码
koa-session 默认的 session 存储方式 cookie,同时也支持外部存储默认配置下,会使用 cookie 来存储 session 信息,也就是实现了一个”cookie session”。这种方式对服务端是比较轻松的,不需要额外记录任何 session 信息,但是也有不少限制,比如大小的限制以及安全性上的顾虑。用 cookie 保存时,实现上非常简单,就是对 session(包括过期时间)序列化后做一个简单的 base64 编码。其结果类似
koa:sess=eyJwYXNzcG9ydCI6eyJ1c2VyIjozMDM0MDg1MTQ4OTcwfSwiX2V4cGlyZSI6MTUxNzI3NDE0MTI5MiwiX21heEFnZSI6ODY0MDAwMDB9;
在实际项目中,会话相关信息往往需要再服务端持久化,因此一般都会使用外部存储来记录 session 信息。外部存储可以是任何的存储系统,可以是内存数据结构,也可以是本地的文件,也可以是远程的数据库。但是这不意味着我们不需要 cookie 了,由于 http 协议的无状态特性,我们依然需要通过 cookie 来获取 session 的标识(这里叫 externalKey )。koa-session 里的 external key 默认是一个时间戳加上一个随机串,因此 cookie 的内容类似
koa:sess=1517188075739-wnRru1LrIv0UFDODDKo8trbmFubnVmMU
原理:
koa-csrf 会在 session 中保存一个secret
字段,创建一个新的密钥。
1 | ctx.session.secret = ** (Create a new secret key synchronously) |
使用密钥生成 token,由于每次请求都是重新生成 salt,因此每次 token 都不一样
1 | # secret是上面生成的密钥 |
验证的时候,只需要取出 token 头部的 salt,再从 session 中取出 secret,再生成 expected,与 token 对比
1 | # 校验函数 |
添加中间件
1 | ... |
创建 ejs 模板,其中_csrf
认证的字段
1 | <form action="/csrf/register" method="POST"> |
ctx.csrf
带有生成的 token
1 | async index(ctx: any) { |
第一次访问的时候服务器会响应 setCookie,后面请求都会
提交时候把_csrf
字段带给服务器校验
相关链接