mirror of
https://github.com/heartcombo/devise.git
synced 2026-05-02 03:01:49 -04:00
Fix security issue in the `Confirmable` "change email" flow, where a user can end up confirming an email address that they have no access to. The flow for this is: 1. Attacker registers `attacker1@email.com` 2. Attacker changes their email to `attacker2@email.com`, but does not yet confirm this 3. Attacker submits two concurrent "change email" requests a. one changing to `attacker2@email.com` b. one changing to `victim@email.com` When request 3.a is run, the `Confirmable.postpone_email_change_until_confirmation_and_regenerate_confirmation_token` method sets both the `unconfirmed_email` and `confirmation_token` properties. But as the `unconfirmed_email` value is the same as the model already had from step 2, this attribute is not included in the SQL `UPDATE` statement. The SQL `UPDATE` statement only updates the `confirmation_token`. This token is emailed to the `attacker2@email.com` address. If the "victim" race request (3.b) completes first, it will update both the `unconfirmed_email` and the `confirmation_token`. But then request 3.a will replace just the token. The model's end state is having the confirmation token that was sent to the attacker, but with the `unconfirmed_email` of the victim. When the attacker follows the confirmation link, they will have confirmed the victim's email address, on an account that the attacker controls. Co-authored-by: Carlos Antonio da Silva <carlosantoniodasilva@gmail.com>