CVE-2022-39355 (Ruby, published 2022-10-26T20:15Z)
Description: value: Discourse Patreon enables syncronization between Discourse Groups and Patreon rewards. On sites with Patreon login enabled, an improper authentication vulnerability could be used to take control of a victim's forum account. This vulnerability is patched in commit number 846d012151514b35ce42a1636c7d70f6dcee879e of the discourse-patreon plugin. Out of an abundance of caution, any Discourse accounts which have logged in with an unverified-email Patreon account will be logged out and asked to verify their email address on their next login. As a workaround, disable the patreon integration and log out all users with associated Patreon accounts.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/discourse/discourse-patreon
Files changed:
db/post_migrate/20221025153038_deactivate_unverified_email_patreon_accounts.rb
db/post_migrate/20221026043851_delete_unverified_patreon_user_info.rb
plugin.rb
Commit message: SECURITY: Check for email verification status during login (#92)

This commit also includes a migration which will deactivate any users with existing connections to unverified-email Patreon accounts. They will be asked to verify their email on their next login.

Vulnerable code:

Fixed code:
# frozen_string_literal: true

class DeactivateUnverifiedEmailPatreonAccounts < ActiveRecord::Migration[6.1]
  def up
    execute <<~SQL
      UPDATE users SET active = false
      WHERE users.id IN (
        SELECT user_id FROM user_associated_accounts
        WHERE provider_name = 'patreon'
        AND extra -> 'raw_info' -> 'data' -> 'attributes' ->> 'is_email_verified' = 'false'
      )
    SQL
  end

  def down
    # noop
  end
end
# frozen_string_literal: true

class DeleteUnverifiedPatreonUserInfo < ActiveRecord::Migration[6.1]
  def up
    execute <<~SQL
      DELETE FROM user_auth_tokens
      WHERE user_id IN (
        SELECT user_id
        FROM user_associated_accounts
        WHERE provider_name = 'patreon'
        AND COALESCE(JSON_EXTRACT_PATH(extra::json, 'raw_info', 'data', 'attributes', 'is_email_verified')::text, 'false') <> 'true'
      )
    SQL

    execute <<~SQL
      UPDATE user_api_keys
      SET revoked_at = NOW()
      WHERE user_id IN (
        SELECT user_id
        FROM user_associated_accounts
        WHERE provider_name = 'patreon'
        AND COALESCE(JSON_EXTRACT_PATH(extra::json, 'raw_info', 'data', 'attributes', 'is_email_verified')::text, 'false') <> 'true'
      )
    SQL

    execute <<~SQL
      UPDATE api_keys
      SET revoked_at = NOW()
      WHERE created_by_id IN (
        SELECT user_id
        FROM user_associated_accounts
        WHERE provider_name = 'patreon'
        AND COALESCE(JSON_EXTRACT_PATH(extra::json, 'raw_info', 'data', 'attributes', 'is_email_verified')::text, 'false') <> 'true'
      )
    SQL

    execute <<~SQL
      DELETE FROM user_associated_accounts
      WHERE provider_name = 'patreon'
      AND COALESCE(JSON_EXTRACT_PATH(extra::json, 'raw_info', 'data', 'attributes', 'is_email_verified')::text, 'false') <> 'true'
    SQL
  end

  def down
    # noop
  end
end
      email_verified: raw_info['data']['attributes']['is_email_verified'],

  def primary_email_verified?(auth_token)
    auth_token[:info][:email_verified]
  end