CVE-2022-31060: Insufficient Information (NVD-CWE-noinfo)
value: Discourse is an open-source discussion platform. Prior to version 2.8.4 in the `stable` branch and version `2.9.0.beta5` in the `beta` and `tests-passed` branches, banner topic data is exposed on login-required sites. This issue is patched in version 2.8.4 in the `stable` branch and version `2.9.0.beta5` in the `beta` and `tests-passed` branches of Discourse. As a workaround, one may disable banners.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 5.3
Published: 2022-06-14T21:15Z
Repository: https://github.com/discourse/discourse
Language: Ruby
Files changed: app/controllers/application_controller.rb, spec/requests/application_controller_spec.rb
Commit date: 2022-06-13 11:10:21 -0600
Commit message: SECURITY: banner-info (#17071)


Diff: diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 274730156eb8..1eaaabdcb6fa 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -674,10 +674,11 @@ class ApplicationController < ActionController::Base
     @banner_json_cache ||= DistributedCache.new("banner_json")
   end
 
   def banner_json
     json = ApplicationController.banner_json_cache["json"]
+    return "{}" if !current_user && SiteSetting.login_required?
 
     unless json
       topic = Topic.where(archetype: Archetype.banner).first
       banner = topic.present? ? topic.banner : {}
       ApplicationController.banner_json_cache["json"] = json = MultiJson.dump(banner)
diff --git a/spec/requests/application_controller_spec.rb b/spec/requests/application_controller_spec.rb
index bbf0fd23695b..0a7c4eb87bc6 100644
--- a/spec/requests/application_controller_spec.rb
+++ b/spec/requests/application_controller_spec.rb
@@ -1000,6 +1000,55 @@ RSpec.describe ApplicationController do
         "HTTP_USER_AGENT" => "iam problematiccrawler"
       }
       expect(response.status).to eq(200)
     end
   end
+
+  describe "#banner_json" do
+    let(:admin) { Fabricate(:admin) }
+    let(:user) { Fabricate(:user) }
+    fab!(:banner_topic) { Fabricate(:banner_topic) }
+    fab!(:p1) { Fabricate(:post, topic: banner_topic, raw: "A banner topic") }
+
+    before do
+      admin # to skip welcome wizard at home page `/`
+    end
+
+    context "login_required" do
+      before do
+        SiteSetting.login_required = true
+      end
+      it "does not include banner info for anonymous users" do
+        get "/login"
+
+        expect(response.body).to have_tag("div#data-preloaded") do |element|
+          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
+          expect(json['banner']).to eq("{}")
+        end
+      end
+
+      it "includes banner info for logged-in users" do
+        sign_in(user)
+        get "/"
+
+        expect(response.body).to have_tag("div#data-preloaded") do |element|
+          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
+          expect(JSON.parse(json['banner'])["html"]).to eq("<p>A banner topic</p>")
+        end
+      end
+    end
+
+    context "login not required" do
+      before do
+        SiteSetting.login_required = false
+      end
+      it "does include banner info for anonymous users" do
+        get "/login"
+
+        expect(response.body).to have_tag("div#data-preloaded") do |element|
+          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
+          expect(JSON.parse(json['banner'])["html"]).to eq("<p>A banner topic</p>")
+        end
+      end
+    end
+  end
 end

Fixed code:     return "{}" if !current_user && SiteSetting.login_required?

  describe "#banner_json" do
    let(:admin) { Fabricate(:admin) }
    let(:user) { Fabricate(:user) }
    fab!(:banner_topic) { Fabricate(:banner_topic) }
    fab!(:p1) { Fabricate(:post, topic: banner_topic, raw: "A banner topic") }

    before do
      admin # to skip welcome wizard at home page `/`
    end

    context "login_required" do
      before do
        SiteSetting.login_required = true
      end
      it "does not include banner info for anonymous users" do
        get "/login"

        expect(response.body).to have_tag("div#data-preloaded") do |element|
          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
          expect(json['banner']).to eq("{}")
        end
      end

      it "includes banner info for logged-in users" do
        sign_in(user)
        get "/"

        expect(response.body).to have_tag("div#data-preloaded") do |element|
          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
          expect(JSON.parse(json['banner'])["html"]).to eq("<p>A banner topic</p>")
        end
      end
    end

    context "login not required" do
      before do
        SiteSetting.login_required = false
      end
      it "does include banner info for anonymous users" do
        get "/login"

        expect(response.body).to have_tag("div#data-preloaded") do |element|
          json = JSON.parse(element.current_scope.attribute('data-preloaded').value)
          expect(JSON.parse(json['banner'])["html"]).to eq("<p>A banner topic</p>")
        end
      end
    end
  end