CSRF Token 生成方法原理

2019/07/31 Work

最近又看了一下CSRF攻击的内容,将CSRF token的生成算法也熟悉了一下,在此做总结。

对CSRF攻击的防御措施主要有两种

  • 检查HTTP Header Referer字段: 通常来说,Referer字段应和请求的地址位于同一域名下
  • 添加校验token

生成CSRF token的具体逻辑,下面使用的是Rails中的生成方法做例子,我看了gorilla/csrf也是类似的方法

# 首先生成真实的raw_token并存到session或cookie中
def real_csrf_token(session) # :doc:
  session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
  Base64.strict_decode64(session[:_csrf_token])
end

# 看到raw_token 只是一个已知长度的随机字符串,然后再base64之后,存到session中
raw_token = real_csrf_token(session)

# 构造给表单hidden或ajax的mask_token

def mask_token
  one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
  encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token) # xor计算
  masked_token = one_time_pad + encrypted_csrf_token
	Base64.strict_encode64(masked_token)
end

# mask_token在前端页面是可以看到的

# 如果进行对比校验?
# 将前端传过来的masked_token 进行unmask 得到一个 unmask_token 再和session中的session[:_csrf_token]进行比较,相等说明校验通过,unmask 的过程就是mask的逆向过程

def unmask_token(masked_token) # :doc:
  one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH]
  encrypted_csrf_token = masked_token[AUTHENTICITY_TOKEN_LENGTH..-1]
  xor_byte_strings(one_time_pad, encrypted_csrf_token)
end

unmask_token == session[:_csrf_token] ? Yes : No

更详细的理解:

  • 在这一次session的生命周期中,session[:_csrf_token] 是固定的
  • 每次给前端的mask_token是不一样的,是因为每次的one_time_pad是不一样的
  • 但是,每次将mask_token unmask逆向之后得到的real_token应该是一样的,然后和session[:_csrf_token]比较即可

所以,如果攻击者要破解CSRF token,他需要能够在session这次生命周期内拿到session[:_csrf_token] 如果session[:_csrf_token]存在cookie中,攻击者需要破解cookie得到真实数据,可以设置cookie的httponly属性为true,这样cookie就只能通过服务器读取不能通过javascript读取

Search

    Table of Contents