diff options
author | Rémy Coutable <remy@gitlab.com> | 2016-04-07 11:56:44 +0000 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2016-04-07 14:39:08 -0400 |
commit | 6da54f2e8f847361e12373fc4be3ec918cc6b901 (patch) | |
tree | c5eea243d74b9ead70221f07733e6239a78ea740 | |
parent | 1419bb1b944ccf1b451013b600fa22b24283879f (diff) | |
download | gitlab-ce-6da54f2e8f847361e12373fc4be3ec918cc6b901.tar.gz |
Merge branch 'fix/2fa-authentication-spoofing' into 'master'
Fix 2FA authentication spoofing
Signed-off-by: Rémy Coutable <remy@rymai.me>
-rw-r--r-- | CHANGELOG | 3 | ||||
-rw-r--r-- | app/controllers/sessions_controller.rb | 15 | ||||
-rw-r--r-- | spec/controllers/sessions_controller_spec.rb | 101 |
3 files changed, 113 insertions, 6 deletions
diff --git a/CHANGELOG b/CHANGELOG index d6868a8face..7b0e5e9ce25 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. +v 8.3.7 + - Fix a 2FA authentication spoofing vulnerability. + v 8.3.6 - Don't attempt to fetch any tags from a forked repo (Stan Hu). diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 825f85199be..d632481516d 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -2,7 +2,8 @@ class SessionsController < Devise::SessionsController include AuthenticatesWithTwoFactor include Recaptcha::ClientHelper - prepend_before_action :authenticate_with_two_factor, only: [:create] + prepend_before_action :authenticate_with_two_factor, + if: :two_factor_enabled?, only: [:create] prepend_before_action :store_redirect_path, only: [:new] before_action :auto_sign_in_with_provider, only: [:new] before_action :load_recaptcha @@ -36,10 +37,10 @@ class SessionsController < Devise::SessionsController end def find_user - if user_params[:login] - User.by_login(user_params[:login]) - elsif user_params[:otp_attempt] && session[:otp_user_id] + if session[:otp_user_id] User.find(session[:otp_user_id]) + elsif user_params[:login] + User.by_login(user_params[:login]) end end @@ -63,11 +64,13 @@ class SessionsController < Devise::SessionsController end end + def two_factor_enabled? + find_user.try(:two_factor_enabled?) + end + def authenticate_with_two_factor user = self.resource = find_user - return unless user && user.two_factor_enabled? - if user_params[:otp_attempt].present? && session[:otp_user_id] if valid_otp_attempt?(user) # Remove any lingering user data from login diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 00000000000..83cc8ec6d26 --- /dev/null +++ b/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe SessionsController do + describe '#create' do + before do + @request.env['devise.mapping'] = Devise.mappings[:user] + end + + context 'when using standard authentications' do + context 'invalid password' do + it 'does not authenticate user' do + post(:create, user: { login: 'invalid', password: 'invalid' }) + + expect(response) + .to set_flash.now[:alert].to /Invalid login or password/ + end + end + + context 'when using valid password' do + let(:user) { create(:user) } + + it 'authenticates user correctly' do + post(:create, user: { login: user.username, password: user.password }) + + expect(response).to set_flash.to /Signed in successfully/ + expect(subject.current_user). to eq user + end + end + end + + context 'when using two-factor authentication' do + let(:user) { create(:user, :two_factor) } + + def authenticate_2fa(user_params) + post(:create, { user: user_params }, { otp_user_id: user.id }) + end + + ## + # See #14900 issue + # + context 'when authenticating with login and OTP of another user' do + context 'when another user has 2FA enabled' do + let(:another_user) { create(:user, :two_factor) } + + context 'when OTP is valid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: another_user.current_otp) + + expect(subject.current_user).to_not eq another_user + end + end + + context 'when OTP is invalid for another user' do + it 'does not authenticate' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') + + expect(subject.current_user).to_not eq another_user + end + end + + context 'when authenticating with OTP' do + context 'when OTP is valid' do + it 'authenticates correctly' do + authenticate_2fa(otp_attempt: user.current_otp) + + expect(subject.current_user).to eq user + end + end + + context 'when OTP is invalid' do + before { authenticate_2fa(otp_attempt: 'invalid') } + + it 'does not authenticate' do + expect(subject.current_user).to_not eq user + end + + it 'warns about invalid OTP code' do + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ + end + end + end + + context 'when another user does not have 2FA enabled' do + let(:another_user) { create(:user) } + + it 'does not leak that 2FA is disabled for another user' do + authenticate_2fa(login: another_user.username, + otp_attempt: 'invalid') + + expect(response).to set_flash.now[:alert] + .to /Invalid two-factor code/ + end + end + end + end + end + end +end |