CVE-2023-36460 (Ruby, published 2023-07-06T19:15Z)
Description: value: Mastodon is a free, open-source social network server based on ActivityPub. Starting in version 3.5.0 and prior to versions 3.5.9, 4.0.5, and 4.1.3, attackers using carefully crafted media files can cause Mastodon's media processing code to create arbitrary files at any location. This allows attackers to create and overwrite any file Mastodon has access to, allowing Denial of Service and arbitrary Remote Code Execution. Versions 3.5.9, 4.0.5, and 4.1.3 contain a patch for this issue.
Severity: nan (CVSS v3 base score: 9.9)
Weakness: CWE-22 \u2014 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Repository: https://github.com/mastodon/mastodon
Files changed:
app/models/concerns/attachmentable.rb
config/application.rb
config/imagemagick/policy.xml
config/initializers/paperclip.rb
lib/paperclip/media_type_spoof_detector_extensions.rb
lib/paperclip/transcoder.rb
spec/models/media_attachment_spec.rb
Commit message: Merge pull request from GHSA-9928-3cp5-93fm

* Fix attachments getting processed despite failing content-type validation

* Add a restrictive ImageMagick security policy tailored for Mastodon

* Fix misdetection of MP3 files with large cover art

* Reject unprocessable audio/video files instead of keeping them unchanged

Vulnerable code:
      options = { validate_media_type: false }.merge(options)
      send(:"before_#{name}_post_process") do
        Paperclip::Validators::MediaTypeSpoofDetectionValidator.new(attributes: [name]).validate(self)
      unless metadata.valid?
        Paperclip.log("Unsupported file #{@file.path}")
        return File.open(@file.path)
      end

Fixed code:

      send(:"before_#{name}_validate") do
require_relative '../lib/paperclip/media_type_spoof_detector_extensions'
<policymap>
  <!-- Set some basic system resource limits -->
  <policy domain="resource" name="time" value="60" />

  <policy domain="module" rights="none" pattern="URL" />

  <policy domain="filter" rights="none" pattern="*" />

  <!--
    Ideally, we would restrict ImageMagick to only accessing its own
    disk-backed pixel cache as well as Mastodon-created Tempfiles.

    However, those paths depend on the operating system and environment
    variables, so they can only be known at runtime.

    Furthermore, those paths are not necessarily shared across Mastodon
    processes, so even creating a policy.xml at runtime is impractical.

    For the time being, only disable indirect reads.
  -->
  <policy domain="path" rights="none" pattern="@*" />

  <!-- Disallow any coder by default, and only enable ones required by Mastodon -->
  <policy domain="coder" rights="none" pattern="*" />
  <policy domain="coder" rights="read | write" pattern="{PNG,JPEG,GIF,HEIC,WEBP}" />
  <policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO}" />
</policymap>

# Set our ImageMagick security policy, but allow admins to override it
ENV['MAGICK_CONFIGURE_PATH'] = begin
  imagemagick_config_paths = ENV.fetch('MAGICK_CONFIGURE_PATH', '').split(File::PATH_SEPARATOR)
  imagemagick_config_paths << Rails.root.join('config', 'imagemagick').expand_path.to_s
  imagemagick_config_paths.join(File::PATH_SEPARATOR)
end
# frozen_string_literal: true

module Paperclip
  module MediaTypeSpoofDetectorExtensions
    def calculated_content_type
      return @calculated_content_type if defined?(@calculated_content_type)

      @calculated_content_type = type_from_file_command.chomp

      # The `file` command fails to recognize some MP3 files as such
      @calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg'
      @calculated_content_type
    end

    def type_from_marcel
      @type_from_marcel ||= Marcel::MimeType.for Pathname.new(@file.path),
                                                 name: @file.path
    end
  end
end

Paperclip::MediaTypeSpoofDetector.prepend(Paperclip::MediaTypeSpoofDetectorExtensions)
      raise Paperclip::Error, "Error while transcoding #{@file.path}: unsupported file" unless metadata.valid?
  describe 'mp3 with large cover art' do
    let(:media) { described_class.create(account: Fabricate(:account), file: attachment_fixture('boop.mp3')) }

    it 'detects it as an audio file' do
      expect(media.type).to eq 'audio'
    end

    it 'sets meta for the duration' do
      expect(media.file.meta['original']['duration']).to be_within(0.05).of(0.235102)
    end

    it 'extracts thumbnail' do
      expect(media.thumbnail.present?).to be true
    end

    it 'gives the file a random name' do
      expect(media.file_file_name).to_not eq 'boop.mp3'
    end
  end