Commit 29022350 authored by blackst0ne's avatar blackst0ne Committed by Douwe Maan

Add CSRF token verification to API

parent f2da36f1
---
title: Add CSRF token verification to API
merge_request: 12154
author: @blackst0ne
...@@ -328,6 +328,33 @@ module API ...@@ -328,6 +328,33 @@ module API
private private
def xor_byte_strings(s1, s2)
s2_bytes = s2.bytes
s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 }
s2_bytes.pack('C*')
end
# Check if CSRF tokens are equal.
# The header token is masked.
# So, before the comparison it must be unmasked.
def csrf_tokens_valid?(request)
session_token = request.session['_csrf_token']
header_token = request.headers['X-Csrf-Token']
session_token = Base64.strict_decode64(session_token)
header_token = Base64.strict_decode64(header_token)
# Decoded CSRF token passed from the frontend has to be 64 symbols long.
return false if header_token.size != 64
header_token = xor_byte_strings(header_token[0...32], header_token[32..-1])
ActiveSupport::SecurityUtils.secure_compare(session_token, header_token)
rescue
false
end
def private_token def private_token
params[APIGuard::PRIVATE_TOKEN_PARAM] || env[APIGuard::PRIVATE_TOKEN_HEADER] params[APIGuard::PRIVATE_TOKEN_PARAM] || env[APIGuard::PRIVATE_TOKEN_HEADER]
end end
...@@ -336,12 +363,15 @@ module API ...@@ -336,12 +363,15 @@ module API
env['warden'] env['warden']
end end
def verified_request?
request = Grape::Request.new(env)
request.head? || request.get? || csrf_tokens_valid?(request)
end
# Check the Rails session for valid authentication details # Check the Rails session for valid authentication details
#
# Until CSRF protection is added to the API, disallow this method for
# state-changing endpoints
def find_user_from_warden def find_user_from_warden
warden.try(:authenticate) if %w[GET HEAD].include?(env['REQUEST_METHOD']) warden.try(:authenticate) if verified_request?
end end
def initial_current_user def initial_current_user
......
require 'spec_helper'
describe API::Helpers do
subject do
Class.new.include(described_class).new
end
let(:header_token) { 'WblCcheb1qQLHFVhlMtwOhxJr5613vUT05vCvToRvfJ68UPT7+eV5xpaY9CjubnF3VGbTfIhQYkZWmWTfvZAWQ==' }
let(:session_token) { 'I0gBofh8Q0MRRjaxN3LJ/8EYNNNH/7SaysGnLkTn/as=' }
before do
class Request
attr_reader :headers
attr_reader :session
def initialize(header_token = nil, session_token = nil)
@headers = { 'X-Csrf-Token' => header_token }
@session = { '_csrf_token' => session_token }
end
end
end
it 'should return false if header token is invalid' do
request = Request.new(nil, session_token)
expect(subject.send(:csrf_tokens_valid?, request)).to be false
end
it 'should return false if session_token token is invalid' do
request = Request.new(header_token, nil)
expect(subject.send(:csrf_tokens_valid?, request)).to be false
end
it 'should return false if header_token is not 64 symbols long' do
request = Request.new(header_token[0..16], session_token)
expect(subject.send(:csrf_tokens_valid?, request)).to be false
end
it 'should return true if both header_token and session_token are correct' do
request = Request.new(header_token, session_token)
expect(subject.send(:csrf_tokens_valid?, request)).to be true
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment