CVE-2021-25975: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: In publify, versions v8.0 to v9.2.4 are vulnerable to stored XSS as a result of an unrestricted file upload. This issue allows a user with “publisher” role to inject malicious JavaScript via the uploaded html file.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 5.4
Published: 2021-11-10T11:15Z
Repository: https://github.com/publify/publify
Language: Ruby
Files changed: publify_core/Manifest.txt, publify_core/app/uploaders/resource_uploader.rb, publify_core/lib/publify_core/testing_support/fixtures/just_some.html, publify_core/spec/controllers/admin/resources_controller_spec.rb
Commit date: 2021-10-11 07:40:14 +0200
Commit message: Only allow certain file types to be uploaded as resources

This is mainly to disallow uploading html files, but there may be other
dangerous file types so using an allowlist is safer.

For now, images, audio, video and plain text are allowed since the
resources are meant to be used as a media library.

Diff: diff --git a/publify_core/Manifest.txt b/publify_core/Manifest.txt
index 34492c696..4baf45aea 100644
--- a/publify_core/Manifest.txt
+++ b/publify_core/Manifest.txt
@@ -415,10 +415,11 @@ lib/publify_core/lang.rb
 lib/publify_core/testing_support/dns_mock.rb
 lib/publify_core/testing_support/factories.rb
 lib/publify_core/testing_support/feed_assertions.rb
 lib/publify_core/testing_support/fixtures/exploit.svg
 lib/publify_core/testing_support/fixtures/fakepng.png
+lib/publify_core/testing_support/fixtures/just_some.html
 lib/publify_core/testing_support/fixtures/otherfile.txt
 lib/publify_core/testing_support/fixtures/testfile.png
 lib/publify_core/testing_support/fixtures/testfile.txt
 lib/publify_core/testing_support/upload_fixtures.rb
 lib/publify_core/version.rb
diff --git a/publify_core/app/uploaders/resource_uploader.rb b/publify_core/app/uploaders/resource_uploader.rb
index f65a4a0ca..2f097417a 100644
--- a/publify_core/app/uploaders/resource_uploader.rb
+++ b/publify_core/app/uploaders/resource_uploader.rb
@@ -4,10 +4,14 @@ require "marcel"
 
 class ResourceUploader < CarrierWave::Uploader::Base
   include CarrierWave::MiniMagick
   before :cache, :check_image_content_type!
 
+  def content_type_allowlist
+    [%r{image/}, %r{audio/}, %r{video/}, "text/plain"]
+  end
+
   def store_dir
     "files/#{model.class.to_s.underscore}/#{model.id}"
   end
 
   version :thumb, if: :image? do
diff --git a/publify_core/lib/publify_core/testing_support/fixtures/just_some.html b/publify_core/lib/publify_core/testing_support/fixtures/just_some.html
new file mode 100644
index 000000000..c592a9f78
--- /dev/null
+++ b/publify_core/lib/publify_core/testing_support/fixtures/just_some.html
@@ -0,0 +1,5 @@
+<html>
+  <body>
+    <p>Hello!</p>
+  </body>
+</html>
diff --git a/publify_core/spec/controllers/admin/resources_controller_spec.rb b/publify_core/spec/controllers/admin/resources_controller_spec.rb
index 137406ac9..85820f686 100644
--- a/publify_core/spec/controllers/admin/resources_controller_spec.rb
+++ b/publify_core/spec/controllers/admin/resources_controller_spec.rb
@@ -158,10 +158,37 @@ describe Admin::ResourcesController, type: :controller do
           expect(flash[:warning]).not_to be_nil
         end
       end
     end
 
+    context "when attempting to upload an html file" do
+      let(:upload) { file_upload("just_some.html", "text/html") }
+
+      it "does not create a new Resource" do
+        expect { post :upload, params: { upload: upload } }.
+          not_to change(Resource, :count)
+      end
+
+      it "warns the user they can't upload this type of file" do
+        post :upload, params: { upload: upload }
+        result = assigns(:up)
+        expect(result.errors[:upload]).
+          to match_array [
+            %r{You are not allowed to upload text/html files},
+            "can't be blank",
+          ]
+      end
+
+      it "sets the flash to failure" do
+        post :upload, params: { upload: upload }
+        aggregate_failures do
+          expect(flash[:success]).to be_nil
+          expect(flash[:warning]).not_to be_nil
+        end
+      end
+    end
+
     context "when uploading nothing" do
       it "does not create a new Resource" do
         expect { post :upload }.
           not_to change(Resource, :count)
       end

Fixed code: lib/publify_core/testing_support/fixtures/just_some.html
  def content_type_allowlist
    [%r{image/}, %r{audio/}, %r{video/}, "text/plain"]
  end

<html>
  <body>
    <p>Hello!</p>
  </body>
</html>
    context "when attempting to upload an html file" do
      let(:upload) { file_upload("just_some.html", "text/html") }

      it "does not create a new Resource" do
        expect { post :upload, params: { upload: upload } }.
          not_to change(Resource, :count)
      end

      it "warns the user they can't upload this type of file" do
        post :upload, params: { upload: upload }
        result = assigns(:up)
        expect(result.errors[:upload]).
          to match_array [
            %r{You are not allowed to upload text/html files},
            "can't be blank",
          ]
      end

      it "sets the flash to failure" do
        post :upload, params: { upload: upload }
        aggregate_failures do
          expect(flash[:success]).to be_nil
          expect(flash[:warning]).not_to be_nil
        end
      end
    end