Fix Session replay secure issue that when Rails application use CookieStore.

This commit is contained in:
Jason Lee 2016-10-14 17:33:34 +08:00
parent 72d9e145db
commit e129851fd9
7 changed files with 98 additions and 26 deletions

View File

@ -1,3 +1,10 @@
1.0.1
-----
## Security Notes
- Fix Session replay secure issue that when Rails application use CookieStore.
1.0.0 1.0.0
----- -----

View File

@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
rucaptcha (1.0.0) rucaptcha (1.0.1)
railties (>= 3.2) railties (>= 3.2)
GEM GEM

View File

@ -11,7 +11,7 @@ module RuCaptcha
attr_accessor :cache_limit attr_accessor :cache_limit
# Color style, default: :colorful, allows: [:colorful, :black_white] # Color style, default: :colorful, allows: [:colorful, :black_white]
attr_accessor :style attr_accessor :style
# session[:_rucaptcha] expire time, default 2 minutes # rucaptcha expire time, default 2 minutes
attr_accessor :expires_in attr_accessor :expires_in
end end
end end

View File

@ -6,28 +6,55 @@ module RuCaptcha
helper_method :verify_rucaptcha? helper_method :verify_rucaptcha?
end end
def generate_rucaptcha def rucaptcha_sesion_key_key
session[:_rucaptcha] = RuCaptcha::Captcha.random_chars ['rucaptcha-session', session.id].join(':')
session[:_rucaptcha_at] = Time.now.to_i end
RuCaptcha::Captcha.create(session[:_rucaptcha]) def generate_rucaptcha
code = RuCaptcha::Captcha.random_chars
Rails.cache.write(rucaptcha_sesion_key_key, {
code: code,
time: Time.now.to_i
})
RuCaptcha::Captcha.create(code)
end end
def verify_rucaptcha?(resource = nil) def verify_rucaptcha?(resource = nil)
rucaptcha_at = session[:_rucaptcha_at].to_i store_info = Rails.cache.read(rucaptcha_sesion_key_key)
# make sure move used key
Rails.cache.delete(rucaptcha_sesion_key_key)
# Make sure session exist
if store_info.blank?
return add_rucaptcha_validation_error
end
# Make sure not expire
if (Time.now.to_i - store_info[:time]) > RuCaptcha.config.expires_in
return add_rucaptcha_validation_error
end
# Make sure parama have captcha
captcha = (params[:_rucaptcha] || '').downcase.strip captcha = (params[:_rucaptcha] || '').downcase.strip
if captcha.blank?
# Captcha chars in Session expire in 2 minutes return add_rucaptcha_validation_error
valid = false
if (Time.now.to_i - rucaptcha_at) <= RuCaptcha.config.expires_in
valid = captcha.present? && captcha == session.delete(:_rucaptcha)
end end
if resource && resource.respond_to?(:errors) if captcha != store_info[:code]
resource.errors.add(:base, t('rucaptcha.invalid')) unless valid return add_rucaptcha_validation_error
end end
valid true
end
private
def add_rucaptcha_validation_error
if defined?(resource) && resource && resource.respond_to?(:errors)
resource.errors.add(:base, t('rucaptcha.invalid'))
end
false
end end
end end
end end

View File

@ -1,3 +1,3 @@
module RuCaptcha module RuCaptcha
VERSION = '1.0.0' VERSION = '1.0.1'
end end

View File

@ -1,23 +1,45 @@
require 'spec_helper' require 'spec_helper'
require 'securerandom'
describe RuCaptcha do describe RuCaptcha do
class CustomSession
attr_accessor :id
def initialize
self.id = SecureRandom.hex
end
end
class Simple < ActionController::Base class Simple < ActionController::Base
def session def session
@session ||= {} @session ||= CustomSession.new
end end
def params def params
@params ||= {} @params ||= {}
end end
def custom_session
Rails.cache.read(self.rucaptcha_sesion_key_key)
end
def clean_custom_session
Rails.cache.delete(self.rucaptcha_sesion_key_key)
end
end end
let(:simple) { Simple.new } let(:simple) { Simple.new }
describe '.rucaptcha_sesion_key_key' do
it 'should work' do
expect(simple.rucaptcha_sesion_key_key).to eq ['rucaptcha-session', simple.session.id].join(':')
end
end
describe '.generate_rucaptcha' do describe '.generate_rucaptcha' do
it 'should work' do it 'should work' do
expect(RuCaptcha::Captcha).to receive(:random_chars).and_return('abcd') expect(RuCaptcha::Captcha).to receive(:random_chars).and_return('abcd')
expect(simple.generate_rucaptcha).not_to be_nil expect(simple.generate_rucaptcha).not_to be_nil
expect(simple.session[:_rucaptcha]).to eq('abcd') expect(simple.custom_session[:code]).to eq('abcd')
end end
end end
@ -29,7 +51,7 @@ describe RuCaptcha do
end end
it 'should work when session[:_rucaptcha] is nil' do it 'should work when session[:_rucaptcha] is nil' do
simple.session[:_rucaptcha] = nil simple.clean_custom_session
simple.params[:_rucaptcha] = 'Abcd' simple.params[:_rucaptcha] = 'Abcd'
expect(simple.verify_rucaptcha?).to eq(false) expect(simple.verify_rucaptcha?).to eq(false)
end end
@ -37,11 +59,18 @@ describe RuCaptcha do
context 'Correct chars in params' do context 'Correct chars in params' do
it 'should work' do it 'should work' do
simple.session[:_rucaptcha_at] = Time.now.to_i Rails.cache.write(simple.rucaptcha_sesion_key_key, {
simple.session[:_rucaptcha] = 'abcd' time: Time.now.to_i,
code: 'abcd'
})
simple.params[:_rucaptcha] = 'Abcd' simple.params[:_rucaptcha] = 'Abcd'
expect(simple.verify_rucaptcha?).to eq(true) expect(simple.verify_rucaptcha?).to eq(true)
simple.session[:_rucaptcha] = 'abcd' expect(simple.custom_session).to eq nil
Rails.cache.write(simple.rucaptcha_sesion_key_key, {
time: Time.now.to_i,
code: 'abcd'
})
simple.params[:_rucaptcha] = 'AbcD' simple.params[:_rucaptcha] = 'AbcD'
expect(simple.verify_rucaptcha?).to eq(true) expect(simple.verify_rucaptcha?).to eq(true)
end end
@ -49,17 +78,22 @@ describe RuCaptcha do
describe 'Incorrect chars' do describe 'Incorrect chars' do
it 'should work' do it 'should work' do
simple.session[:_rucaptcha_at] = Time.now.to_i - 60 Rails.cache.write(simple.rucaptcha_sesion_key_key, {
simple.session[:_rucaptcha] = 'abcd' time: Time.now.to_i - 60,
code: 'abcd'
})
simple.params[:_rucaptcha] = 'd123' simple.params[:_rucaptcha] = 'd123'
expect(simple.verify_rucaptcha?).to eq(false) expect(simple.verify_rucaptcha?).to eq(false)
expect(simple.custom_session).to eq nil
end end
end end
describe 'Expires Session key' do describe 'Expires Session key' do
it 'should work' do it 'should work' do
simple.session[:_rucaptcha_at] = Time.now.to_i - 121 Rails.cache.write(simple.rucaptcha_sesion_key_key, {
simple.session[:_rucaptcha] = 'abcd' time: Time.now.to_i - 121,
code: 'abcd'
})
simple.params[:_rucaptcha] = 'abcd' simple.params[:_rucaptcha] = 'abcd'
expect(simple.verify_rucaptcha?).to eq(false) expect(simple.verify_rucaptcha?).to eq(false)
end end

View File

@ -13,6 +13,10 @@ module Rails
def root def root
Pathname.new(File.join(File.dirname(__FILE__), '..')) Pathname.new(File.join(File.dirname(__FILE__), '..'))
end end
def cache
@cache ||= ActiveSupport::Cache::MemoryStore.new
end
end end
end end