Skip to content

Instantly share code, notes, and snippets.

@shotat
Created March 15, 2017 06:15
Show Gist options
  • Save shotat/b967b015c72d0b4dd7f86e773117b8cc to your computer and use it in GitHub Desktop.
Save shotat/b967b015c72d0b4dd7f86e773117b8cc to your computer and use it in GitHub Desktop.
Railsのcsrf対策処理のencodingと検証処理をざっくり
require 'securerandom'
require 'base64'
# https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/csrf_helper.rb#L20-L27
# token = 'jYZcRG05o2IRERhLVqwNVZREdUe4XB0okJRcBUrBQlQ='
# <meta name="csrf-param" content="authenticity_token" />
# <meta name="csrf-token" content="sZu120+MWcK2pEomGML/5SsRSgmGaYOwt0ABKTu663MV0FPC2ssoQuhRl2FXRAn+jEreDewPRIq0ZRh4AoTfxA==" />
class Csrf
AUTHENTICITY_TOKEN_LENGTH = 32
attr_accessor :session
def initialize(session)
self.session = session
end
# viewに埋め込むtokenのkey
def request_forgery_protection_token
:authenticity_token
end
# viewに埋め込むtoken
def form_authenticity_token
masked_authenticity_token(session)
end
# GET, HEADの時はチェックしない
# bodyの'authenticity_token'かheaderの'X-CSRF-Token'を読む
def verified_request?(encoded_masked_token)
# request.get? || request.head? ||
# valid_authenticity_token?(session, form_authenticity_param) ||
# valid_authenticity_token?(session, request.headers['X-CSRF-Token'])
valid_authenticity_token?(session, encoded_masked_token)
end
def valid_authenticity_token?(session, encoded_masked_token) # :doc:
if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String)
return false
end
begin
masked_token = Base64.strict_decode64(encoded_masked_token)
rescue ArgumentError # encoded_masked_token is invalid Base64
return false
end
# See if it's actually a masked token or not. In order to
# deploy this code, we should be able to handle any unmasked
# tokens that we've issued without error.
if masked_token.length == AUTHENTICITY_TOKEN_LENGTH
# This is actually an unmasked token. This is expected if
# you have just upgraded to masked tokens, but should stop
# happening shortly after installing this gem.
compare_with_real_token masked_token, session
elsif masked_token.length == AUTHENTICITY_TOKEN_LENGTH * 2
csrf_token = unmask_token(masked_token)
compare_with_real_token(csrf_token, session)
else
false # Token is malformed.
end
end
private
def compare_with_real_token(token, session) # :doc:
secure_compare(token, real_csrf_token(session))
end
def secure_compare(a, b)
return false unless a.bytesize == b.bytesize
l = a.unpack "C#{a.bytesize}"
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
def unmask_token(masked_token) # :doc:
# Split the token into the one-time pad and the encrypted
# value and decrypt it.
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
def masked_authenticity_token(session) # :doc:
raw_token = real_csrf_token(session)
one_time_pad = SecureRandom.random_bytes(AUTHENTICITY_TOKEN_LENGTH)
encrypted_csrf_token = xor_byte_strings(one_time_pad, raw_token)
masked_token = one_time_pad + encrypted_csrf_token
Base64.strict_encode64(masked_token)
end
def xor_byte_strings(s1, s2)
s1.bytes.zip(s2.bytes).map { |(c1, c2)| c1 ^ c2 }.pack('c*')
end
def real_csrf_token(session) # :doc:
session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH)
Base64.strict_decode64(session[:_csrf_token])
end
end
token = 'jYZcRG05o2IRERhLVqwNVZREdUe4XB0okJRcBUrBQlQ='
session = { _csrf_token: token }
csrf = Csrf.new(session)
encoded_masked_token = csrf.form_authenticity_token
p encoded_masked_token
#=> "WKsUsDYvg3ao2ekV6n2ecpWEsG9GF0gHJp0LSsWj7vfVLUj0WxYgFLnI8V680ZMnAcDFKP5LVS+2CVdPj2Ksow=="
invalid_encoded_masked_token = 'a' * encoded_masked_token.length
p invalid_encoded_masked_token
#=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
p csrf.verified_request?(encoded_masked_token)
#=> true
p csrf.verified_request?(invalid_encoded_masked_token)
#=> false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment