CVE-2022-21677 (Ruby, published 2022-01-14T17:15Z)
Description: value: Discourse is an open source discussion platform. Discourse groups can be configured with varying visibility levels for the group as well as the group members. By default, a newly created group has its visibility set to public and the group's members visibility set to public as well. However, a group's visibility and the group's members visibility can be configured such that it is restricted to logged on users, members of the group or staff users. A vulnerability has been discovered in versions prior to 2.7.13 and 2.8.0.beta11 where the group advanced search option does not respect the group's visibility and members visibility level. As such, a group with restricted visibility or members visibility can be revealed through search with the right search option. This issue is patched in `stable` version 2.7.13, `beta` version 2.8.0.beta11, and `tests-passed` version 2.8.0.beta11 versions of Discourse. There are no workarounds aside from upgrading.
Severity: MEDIUM (CVSS v3 base score: 5.3)
Weakness: CWE-200 \u2014 Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
Repository: https://github.com/discourse/discourse
Files changed:
lib/search.rb
spec/components/search_spec.rb
Commit message: SECURITY: Advanced group search did not respect visiblity of groups.


Vulnerable code:
    group_id = Group.where('name ilike ? OR (id = ? AND id > 0)', match, match.to_i).pluck_first(:id)
    it 'supports group' do
      topic = Fabricate(:topic, created_at: 3.months.ago)
      post = Fabricate(:post, raw: 'hi this is a test 123 123', topic: topic)
      group = Group.create!(name: "Like_a_Boss")
      GroupUser.create!(user_id: post.user_id, group_id: group.id)
      expect(Search.execute('group:like_a_boss').posts.length).to eq(1)
      expect(Search.execute('group:"like a brick"').posts.length).to eq(0)

Fixed code:
    group_id = Group
      .visible_groups(@guardian.user)
      .members_visible_groups(@guardian.user)
      .where('name ilike ? OR (id = ? AND id > 0)', match, match.to_i).pluck_first(:id)

    context "searching for posts made by users of a group" do
      fab!(:topic) { Fabricate(:topic, created_at: 3.months.ago) }
      fab!(:user) { Fabricate(:user) }
      fab!(:user_2) { Fabricate(:user) }
      fab!(:user_3) { Fabricate(:user) }
      fab!(:group) { Fabricate(:group, name: "Like_a_Boss").tap { |g| g.add(user) } }
      fab!(:group_2) { Fabricate(:group).tap { |g| g.add(user_2) } }
      let!(:post) { Fabricate(:post, raw: 'hi this is a test 123 123', topic: topic, user: user) }
      let!(:post_2) { Fabricate(:post, user: user_2) }

      it 'should not return any posts if group does not exist' do
        group.update!(
          visibility_level: Group.visibility_levels[:public],
          members_visibility_level: Group.visibility_levels[:public]
        )

        expect(Search.execute('group:99999').posts).to eq([])
      end

      it 'should return the right posts for a public group' do
        group.update!(
          visibility_level: Group.visibility_levels[:public],
          members_visibility_level: Group.visibility_levels[:public]
        )
        expect(Search.execute('group:like_a_boss').posts).to contain_exactly(post)
        expect(Search.execute("group:#{group.id}").posts).to contain_exactly(post)
      end
      it "should return the right posts for a public group with members' visibility restricted to logged on users" do
        group.update!(
          visibility_level: Group.visibility_levels[:public],
          members_visibility_level: Group.visibility_levels[:logged_on_users]
        )

        expect(Search.execute("group:#{group.id}").posts).to eq([])
        expect(Search.execute("group:#{group.id}", guardian: Guardian.new(user_3)).posts).to contain_exactly(post)
      end

      it "should return the right posts for a group with visibility restricted to logged on users with members' visibility restricted to members" do
        group.update!(
          visibility_level: Group.visibility_levels[:logged_on_users],
          members_visibility_level: Group.visibility_levels[:members]
        )

        expect(Search.execute("group:#{group.id}").posts).to eq([])
        expect(Search.execute("group:#{group.id}", guardian: Guardian.new(user_3)).posts).to eq([])
        expect(Search.execute("group:#{group.id}", guardian: Guardian.new(user)).posts).to contain_exactly(post)
      end