CVE-2026-44707
Description
Chatwoot is a customer engagement suite. From 2.14.0 to before 4.13.0, a Pre-Account Takeover (Pre-ATO) vulnerability existed in Chatwoot's authentication flow. Because email confirmation was not enforced before an account became usable, an attacker could pre-register an email address they did not own and set a password. If the legitimate owner of that email later signed in to Chatwoot using Google OAuth (or another OmniAuth provider), the OAuth flow silently confirmed the existing account without invalidating the attacker's pre-set credentials. The attacker could then continue to log in with the password they had originally chosen and access any data the victim subsequently entered into the dashboard, including PII, API keys, and other sensitive information. This vulnerability is fixed in 4.13.0.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Chatwoot 2.14.0 to before 4.13.0 allowed a pre-account takeover via OAuth: an attacker could register an unused email and still login after the real owner signed up via OAuth.
Vulnerability
Chatwoot versions 2.14.0 through 4.12.x (fixed in 4.13.0) did not enforce email confirmation before an account became usable. This allowed an attacker to pre-register any email address they did not own and set a password of their choice. The vulnerable code path resides in the OAuth callback controller (OmniAuthController), where skip_confirmation! was called before verifying whether the user had already been confirmed.
Exploitation
An attacker requires no prior authentication or special network position. The attacker pre-registers an email address (e.g., using a form on the Chatwoot instance) and sets a password. The legitimate owner of that email then signs in for the first time using Google OAuth (or another OmniAuth provider). The OAuth flow silently confirms the existing unconfirmed account without invalidating the attacker's pre-set password. The attacker can subsequently log in with the originally chosen password at any time.
Impact
Once the victim begins using the dashboard, the attacker—who still holds valid credentials—can log in and access all data entered by the victim, including personally identifiable information (PII), API keys, and other sensitive customer data. This constitutes full compromise of the victim's Chatwoot account (confidentiality and integrity breach) at the same privilege level as the victim (agent or administrator).
Mitigation
The issue is patched in Chatwoot v4.13.0 (commit 211fb11 [2], PR #13878 [1]). The fix captures oauth_user_needs_password_reset? before confirmation and rotates the stored password to a random string if the user was unconfirmed. Users are strongly urged to upgrade to 4.13.0 or later. If immediate upgrade is not possible, workarounds include: disabling OAuth sign-in providers, auditing accounts where confirmed_at was set after email/password sign-up, and forcing a password reset for all users [3].
AI Insight generated on May 26, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
1211fb1102dd2chore: rotate oauth password if unconfirmed (#13878)
2 files changed · +34 −0
app/controllers/devise_overrides/omniauth_callbacks_controller.rb+18 −0 modified@@ -10,7 +10,12 @@ def omniauth_success private def sign_in_user + # Capture before skip_confirmation! sets confirmed_at, which would + # make oauth_user_needs_password_reset? return false and skip the + # password reset for persisted unconfirmed users. + needs_password_reset = oauth_user_needs_password_reset? @resource.skip_confirmation! if confirmable_enabled? + set_random_password_if_oauth_user if needs_password_reset # once the resource is found and verified # we can just send them to the login page again with the SSO params @@ -20,7 +25,10 @@ def sign_in_user end def sign_in_user_on_mobile + # See comment in sign_in_user for why this is captured before skip_confirmation! + needs_password_reset = oauth_user_needs_password_reset? @resource.skip_confirmation! if confirmable_enabled? + set_random_password_if_oauth_user if needs_password_reset # once the resource is found and verified # we can just send them to the login page again with the SSO params @@ -37,6 +45,7 @@ def sign_up_user return redirect_to login_page_url(error: 'business-account-only') unless validate_signup_email_is_business_domain? create_account_for_user + set_random_password_if_oauth_user token = @resource.send(:set_reset_password_token) frontend_url = ENV.fetch('FRONTEND_URL', nil) redirect_to "#{frontend_url}/app/auth/password/edit?config=default&reset_password_token=#{token}" @@ -81,6 +90,15 @@ def create_account_for_user Avatar::AvatarFromUrlJob.perform_later(@resource, auth_hash['info']['image']) end + def oauth_user_needs_password_reset? + @resource.present? && (@resource.new_record? || !@resource.confirmed?) + end + + def set_random_password_if_oauth_user + # Password must satisfy secure_password requirements (uppercase, lowercase, number, special char) + @resource.update(password: "#{SecureRandom.hex(16)}aA1!") if @resource.persisted? + end + def default_devise_mapping 'user' end
spec/controllers/devise/omniauth_callbacks_controller_spec.rb+16 −0 modified@@ -164,5 +164,21 @@ def set_omniauth_config(for_email = 'test@example.com') expect(response).to have_http_status(:ok) end end + + it 'resets password for an unconfirmed persisted user on OAuth login' do + with_modified_env FRONTEND_URL: 'http://www.example.com' do + user = create(:user, email: 'unconfirmed-oauth@example.com', skip_confirmation: false) + original_password_digest = user.encrypted_password + set_omniauth_config('unconfirmed-oauth@example.com') + + get '/omniauth/google_oauth2/callback' + expect(response).to redirect_to('http://www.example.com/auth/google_oauth2/callback') + follow_redirect! + + user.reload + expect(user).to be_confirmed + expect(user.encrypted_password).not_to eq(original_password_digest) + end + end end end
Vulnerability mechanics
Root cause
"Missing password rotation on OAuth confirmation allows an attacker who pre-registered an unowned email to retain access after the legitimate owner authenticates."
Attack vector
An attacker pre-registers an email address they do not own, setting a password of their choice. Because email confirmation is not enforced before the account becomes usable, the account is created in an unconfirmed state. When the legitimate owner of that email later signs in via Google OAuth (or another OmniAuth provider), the OAuth flow silently confirms the account but, prior to the fix, did not rotate the attacker's password. The attacker retains the ability to log in with the originally chosen password and can access any data the victim subsequently enters into the Chatwoot dashboard, including PII, API keys, and other sensitive information [ref_id=1].
Affected code
The vulnerability resides in the OmniAuth callback controller at `app/controllers/devise_overrides/omniauth_callbacks_controller.rb`. The `sign_in_user` and `sign_in_user_on_mobile` methods called `skip_confirmation!` before checking whether the user was unconfirmed, meaning an attacker's pre-set password was never invalidated when the legitimate email owner later authenticated via OAuth [patch_id=2566841].
What the fix does
The patch adds two helper methods: `oauth_user_needs_password_reset?` (which returns true if the user is new or unconfirmed) and `set_random_password_if_oauth_user` (which replaces the stored password with a secure random string). Crucially, the password-reset check is captured *before* `skip_confirmation!` runs, because confirmation would set `confirmed_at` and mask the unconfirmed condition. This logic is applied in `sign_in_user`, `sign_in_user_on_mobile`, and the `sign_up_user` path. Users who lose password-based access can recover via the standard "Forgot password" flow, which is low-friction since they have already proven email ownership through OAuth [patch_id=2566841][ref_id=1].
Preconditions
- inputThe attacker must pre-register an email address they do not own and set a password before the legitimate owner signs up.
- authThe legitimate email owner must later sign in using Google OAuth or another OmniAuth provider.
- configThe Chatwoot instance must have email confirmation enabled (confirmable) but not enforced before account use.
Generated on May 26, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
0No linked articles in our index yet.