常见检查 csrf token方式
在表单里隐藏一个随机变化的 token,当用户提交表单时,将这个 token 提交到后台进行验证,如验证通过则可以继续执行操作,这种情况有效的主要原因是网站 B 拿不到网站 A 表单里的 token。
当向服务器发出请求时生成一个随机值,将这个随机值既放在 cookie 中,也放在请求的参数中,服务器同时验证这两个值是否匹配。
csrf攻击防护方案
如果是post请求
{
如果cookie_csrf有
{
连接redis以csrf_.. ngx.var.cookie_csrf为key查找
如果找到了返回value和ok,否则 not found
返回ok后从请求参数中get_post_args得到csrf字段,如果这个csrf字段value
和从redis查找的value一致
那么放行,ngx.var.csrf_validate = ngx.HTTP_OK
然后proxy_set_header X-Csrf-Valid $csrf_validate; --把结果作为头传给backend
}
}
请求 判断源站返回请求头是否有upstream_http_x_set_csrf
{
判断cookie里是否有cookie_csrf
如果没有random产生一个random_token,然后Set_Cookie到cookie的csrf=random_token,(key)
然后再random生一个csrf_form_token,连接redis把csrf_random_token为key,value为csrf_form_token保存起来
然后在body里增加csrf_form_token
ngx.arg[1] = ngx.re.gsub(ngx.arg[1], "::csrf::", ngx.var.csrf_form_token)
}
配置及代码
server {
listen 80;
root /root/docroot;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Request-URI $request_uri;
proxy_set_header X-Backend example;
proxy_pass_header Set-Cookie;
location /validate-csrf {
internal;
content_by_lua
'
if ngx.var.cookie_csrf then
-- 如果有csrf cookie则验证
local redis = require "redis"
local client = redis.connect("127.0.0.1", 6379)
local csrf_cookie = "csrf_" .. ngx.var.cookie_csrf
local value = client:get(csrf_cookie)
if value then
ngx.say(value)
return ngx.exit(ngx.HTTP_OK)
end
end
-- not found cookie or csrf
return ngx.exit(ngx.HTTP_NOT_FOUND)
';
}
location @backend {
# 自定义变量
set $csrf_validate "";
access_by_lua
'
if ngx.req.get_method() == "POST" then
-- 默认设置长forbidden
ngx.var.csrf_validate = ngx.HTTP_FORBIDDEN
if ngx.var.cookie_csrf then
local res = ngx.location.capture("/validate-csrf")
if ngx.HTTP_OK == res.status then
ngx.req.read_body()
local args = ngx.req.get_post_args()
local posted_token = tostring(args["csrf"])
# 验证步骤
if posted_token == res.body then
ngx.var.csrf_validate = ngx.HTTP_OK
end
end
end
end
';
proxy_set_header X-Csrf-Valid $csrf_validate;
proxy_pass http://127.0.0.1:6081;
# 以尽可能轻量级的方式过滤来自后端的响应
set $csrf_form_token "";
set $csrf_cookie_token "";
header_filter_by_lua
'
if ngx.var.upstream_http_x_set_csrf then
-- 后端请求设置CSRFtoken
local csrf_cookie_token = nil
if ngx.var.cookie_csrf then
local csrf_cookie_token = ngx.var.cookie_csrf
end
local resty_random = require "resty.random"
local str = require "resty.string"
if not csrf_cookie_token then
-- 找不到有效的csrf cookie 手动产生一个
local cookie_random = resty_random.bytes(16,true)
while cookie_random == nil do
cookie_random = resty_random.bytes(16,true)
end
ngx.var.csrf_cookie_token = str.to_hex(cookie_random)
end
ngx.header.Content_Length = ""
ngx.header.Set_Cookie = "csrf=" .. ngx.var.csrf_cookie_token
--现在为表单token生成一个
while form_random == nil do
form_random = resty_random.bytes(16,true)
end
ngx.var.csrf_form_token = str.to_hex(form_random)
-- 将这两个随机数保存到redis
local redis = require "redis"
local client = redis.connect("127.0.0.1", 6379) -- change as appropriate
client:set("csrf_" .. ngx.var.csrf_cookie_token, ngx.var.csrf_form_token)
end
';
# 解析csrf body 并替换内容
body_filter_by_lua
'
if ngx.var.csrf_form_token then
ngx.arg[1] = ngx.re.gsub(ngx.arg[1], "::csrf::", ngx.var.csrf_form_token)
end
';
}
}