Unconfirmed user with `unconfirmed_email` set can confirm that email if it is not reserved without being an owner of that email
While investigating https://gitlab.com/gitlab-com/sec-sub-department/section-sec-request-for-help/-/issues/456, I noticed that there are 1437 user accounts on .com with unconfirmed state(users.confirmed_at IS NULL) and with unconfirmed_email attribute set.
gitlabhq_dblab=# SELECT COUNT(*) FROM users WHERE users.unconfirmed_email IS NOT NULL AND users.confirmed_at IS NULL;
count
-------
1437
(1 row)
Most of those user accounts were created in 2020.
INFO: User has
unconfirmed_emailattribute.unconfirmed_emailattribute is used when user change their primaryunconfirmed_emailvalue and then resetsunconfirmed_email.
While Email confirmation settings is set to Soft for GitLab instance this is a completely valid user state - unconfirmed user can sign in and try to change their primary email and that will set unconfirmed_email. But, as I understand on .com instance, Email confirmation settings is set to Hard, hence such user state should not be possible. It is worth investigating whether there is any way to set unconfirmed_email for unconfirmed user in the GitLab application when the setting is set to Hard. Maybe in the past we temporarily were changing this setting on .com and it is the reason why we have 1437 user accounts with that state?
Problem
The problem is that any of those unconfirmed users can confirm their unconfirmed_email if that email is not being used by another account without being an owner of that email, because we send verification code to user.email and we rely on user.confirm method to confirm the user and it confirms unconfirmed_email.
Another problem is that when unconfirmed user tries to confirm their account but their unconfirmed_email is being used by another account, they won't be able to confirm their account and the only way to resolve this issue is The Support team manually should reset unconfirmed_email for the user account from the Rails console. See https://gitlab.com/gitlab-com/sec-sub-department/section-sec-request-for-help/-/issues/456.
We should reset unconfirmed_email before we confirm the user in https://gitlab.com/gitlab-org/gitlab/-/blob/bcdcde13be6921fca05dbf0da1533a252b9beca3/ee/app/controllers/users/registrations_identity_verification_controller.rb#L111 (since we send the verification code to user.email) to prevent this bugvulnerability and eliminate the need for manual resets for those user accounts' unconfirmed_email.
PoC:
diff --git ee/app/controllers/users/registrations_identity_verification_controller.rb ee/app/controllers/users/registrations_identity_verification_controller.rb
index 10cbb395a5c6..bb8d9a3a4e4e 100644
--- ee/app/controllers/users/registrations_identity_verification_controller.rb
+++ ee/app/controllers/users/registrations_identity_verification_controller.rb
@@ -108,6 +108,7 @@ def verify_token
end
def confirm_user
+ @user.update_column(:unconfirmed_email, nil)
@user.confirm
log_event(:email, :success)
end
Whether the root cause of the issue in the devise gem? And whether user.confirm method should be changed?
No, it is not the devise's issue. When a user has unconfirmed_email set, devise sends confirmation instructions to that email instead of user.email.
user.confirm should not be changed as it behaves correctly and changing it will break the application.
Steps to reproduce
- Set Email confirmation settings to Hard for GitLab instance.
- Visit
/users/sign_upand create user account withgitlab-issue-505444@example.comemail. - Set
unconfirmed_emailfor the user account from the Rails console:
User.find_by(email: 'gitlab-issue-505444@example.com').update(unconfirmed_email: 'gitlab-issue-505444-unconfirmed@example.com')
- On
/users/identity_verificationpage clickSend a new code. You will see that confirmation code is sent togitlab-issue-505444@example.com. - Verify the user account by using that code.
- Go to the user profile, you will see that the user's email is set to
gitlab-issue-505444-unconfirmed@example.com