CVE-2022-21684: Improper Authentication (CWE-287)
value: Discourse is an open source discussion platform. Versions prior to 2.7.13 in `stable`, 2.8.0.beta11 in `beta`, and 2.8.0.beta11 in `tests-passed` allow some users to log in to a community before they should be able to do so. A user invited via email to a forum with `must_approve_users` enabled is going to be automatically logged in, bypassing the check that does not allow unapproved users to sign in. They will be able to do everything an approved user can do. If they logout, they cannot log back in. This issue is patched in the `stable` version 2.7.13, `beta` version 2.8.0.beta11, and `tests-passed` version 2.8.0.beta11. One may disable invites as a workaround. Administrators can increase `min_trust_level_to_allow_invite` to reduce the attack surface to more trusted users.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 8.8
Published: 2022-01-13T21:15Z
Repository: https://github.com/discourse/discourse
Language: Ruby
Files changed: app/controllers/invites_controller.rb, spec/requests/invites_controller_spec.rb
Commit date: 2022-01-12 22:24:54 +0200
Commit message: SECURITY: Do not sign in unapproved users (#15552)


Diff: diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 408b4004d262..582d89af32f5 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -296,27 +296,32 @@ class InvitesController < ApplicationController
 
       if user.blank?
         return render json: failed_json.merge(message: I18n.t('invite.not_found_json')), status: 404
       end
 
-      log_on_user(user) if user.active?
+      log_on_user(user) if user.active? && user.guardian.can_access_forum?
       user.update_timezone_if_missing(params[:timezone])
       post_process_invite(user)
       create_topic_invite_notifications(invite, user)
 
       topic = invite.topics.first
       response = {}
 
       if user.present?
-        if user.active?
+        if user.active? && user.guardian.can_access_forum?
           if user.guardian.can_see?(topic)
             response[:redirect_to] = path(topic.relative_url)
           else
             response[:redirect_to] = path("/")
           end
         else
-          response[:message] = I18n.t('invite.confirm_email')
+          response[:message] = if user.active?
+            I18n.t('activation.approval_required')
+          else
+            I18n.t('invite.confirm_email')
+          end
+
           if user.guardian.can_see?(topic)
             cookies[:destination_url] = path(topic.relative_url)
           end
         end
       end
diff --git a/spec/requests/invites_controller_spec.rb b/spec/requests/invites_controller_spec.rb
index 30564fc94f61..7110cf17335d 100644
--- a/spec/requests/invites_controller_spec.rb
+++ b/spec/requests/invites_controller_spec.rb
@@ -508,10 +508,26 @@ describe InvitesController do
       it 'does not log in the user if there are validation errors' do
         put "/invites/show/#{invite.invite_key}.json", params: { password: 'password' }
         expect(response.status).to eq(412)
       end
 
+      it 'does not log in the user if they were not approved' do
+        SiteSetting.must_approve_users = true
+
+        put "/invites/show/#{invite.invite_key}.json", params: { password: SecureRandom.hex, email_token: invite.email_token }
+
+        expect(session[:current_user_id]).to eq(nil)
+        expect(response.parsed_body["message"]).to eq(I18n.t('activation.approval_required'))
+      end
+
+      it 'does not log in the user if they were not activated' do
+        put "/invites/show/#{invite.invite_key}.json", params: { password: SecureRandom.hex }
+
+        expect(session[:current_user_id]).to eq(nil)
+        expect(response.parsed_body["message"]).to eq(I18n.t('invite.confirm_email'))
+      end
+
       it 'fails when local login is disabled and no external auth is configured' do
         SiteSetting.enable_local_logins = false
 
         put "/invites/show/#{invite.invite_key}.json"
         expect(response.status).to eq(404)

Vulnerable code:       log_on_user(user) if user.active?
        if user.active?
          response[:message] = I18n.t('invite.confirm_email')
Fixed code:       log_on_user(user) if user.active? && user.guardian.can_access_forum?
        if user.active? && user.guardian.can_access_forum?
          response[:message] = if user.active?
            I18n.t('activation.approval_required')
          else
            I18n.t('invite.confirm_email')
          end

      it 'does not log in the user if they were not approved' do
        SiteSetting.must_approve_users = true

        put "/invites/show/#{invite.invite_key}.json", params: { password: SecureRandom.hex, email_token: invite.email_token }

        expect(session[:current_user_id]).to eq(nil)
        expect(response.parsed_body["message"]).to eq(I18n.t('activation.approval_required'))
      end

      it 'does not log in the user if they were not activated' do
        put "/invites/show/#{invite.invite_key}.json", params: { password: SecureRandom.hex }

        expect(session[:current_user_id]).to eq(nil)
        expect(response.parsed_body["message"]).to eq(I18n.t('invite.confirm_email'))
      end