Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Date: Tue, 27 Nov 2018 16:11:36 -0500
From: Rafael Mendonça França <rafaelmfranca@...il.com>
To: Rubyonrails-Security <rubyonrails-security@...glegroups.com>, 
	Ruby-Security-Ann <ruby-security-ann@...glegroups.com>, 
	Oss-Security <oss-security@...ts.openwall.com>
Subject: [CVE-2018-16477] Bypass vulnerability in Active Storage

There is a vulnerability in Active Storage. This vulnerability has been
assigned the CVE identifier CVE-2018-16477.

Versions Affected:  >= 5.2.0
Not affected:       < 5.2.0
Fixed Versions:     5.2.1.1

Impact
------
Signed download URLs generated by `ActiveStorage` for Google Cloud Storage
service and Disk service include `content-disposition` and `content-type`
parameters that an attacker can modify. This can be used to upload specially
crafted HTML files and have them served and executed inline. Combined with
other techniques such as cookie bombing and specially crafted AppCache
manifests,
an attacker can gain access to private signed URLs within a specific
storage path.

Vulnerable apps are those using either GCS or the Disk service in
production.
Other storage services such as S3 or Azure aren't affected.

All users running an affected release should either upgrade or use one of
the
workarounds immediately. For those using GCS, it's also recommended to run
the
following to update existing blobs:

```
ActiveStorage::Blob.find_each do |blob|
  blob.send :update_service_metadata
end
```

Releases
--------
The FIXED releases are available at the normal locations.

Workarounds
-----------
Putting the following monkey patches in an intializer can help to mitigate
the issue:

For GCS service:
```
require 'active_storage'
require 'active_storage/service/gcs_service'

module ActiveStorage
  module GCSMetadata
    def upload(key, io, checksum: nil, content_type: nil, disposition: nil,
filename: nil)
      instrument :upload, key: key, checksum: checksum do
        begin
          content_disposition = content_disposition_with(type: disposition,
filename: filename) if disposition && filename
          bucket.create_file(io, key, md5: checksum, content_type:
content_type, content_disposition: content_disposition)
        rescue Google::Cloud::InvalidArgumentError
          raise ActiveStorage::IntegrityError
        end
      end
    end

    def update_metadata(key, content_type:, disposition: nil, filename: nil)
      instrument :update_metadata, key: key, content_type: content_type,
disposition: disposition do
        file_for(key).update do |file|
          file.content_type = content_type
          if disposition && filename
            file.content_disposition = content_disposition_with(type:
disposition, filename: filename)
          end
        end
      end
    end
  end

  module StoreMetadata
    def upload_without_unfurling(io)
      service.upload key, io, checksum: checksum, **service_metadata
    end

    def identify
      unless identified?
        update! content_type: identify_content_type, identified: true
        update_service_metadata
      end
    end

    private
      def service_metadata
        if forcibly_serve_as_binary?
          { content_type: "application/octet-stream", disposition:
:attachment, filename: filename }
        else
          { content_type: content_type }
        end
      end

      def update_service_metadata
        service.update_metadata key, service_metadata if
service_metadata.any?
      end
  end
end

Rails.application.config.to_prepare do
  ActiveStorage::Service::GCSService.prepend ActiveStorage::GCSMetadata
  ActiveStorage::Blob.prepend ActiveStorage::StoreMetadata
end
```

For Disk service:
```
require 'active_storage'
require 'active_storage/service/disk_service'

module ActiveStorage
  module GetParamsFromKey
    def show
      if key = decode_verified_key
        serve_file disk_service.path_for(key[:key]), content_type:
key[:content_type], disposition: key[:disposition]
      else
        super
      end
    rescue Errno::ENOENT
      head :not_found
    end
  end

  module IncludeParamsInKey
    def upload(key, io, checksum: nil, **)
      super(key, io, checksum: checksum)
    end

    def update_metadata(key, **)
    end

    def url(key, expires_in:, filename:, disposition:, content_type:)
      instrument :url, key: key do |payload|
        content_disposition = content_disposition_with(type: disposition,
filename: filename)
        verified_key_with_expiration = ActiveStorage.verifier.generate(
          {
            key: key,
            disposition: content_disposition,
            content_type: content_type
          },
          { expires_in: expires_in,
          purpose: :blob_key }
        )

        generated_url =
url_helpers.rails_disk_service_url(verified_key_with_expiration,
          host: current_host,
          disposition: content_disposition,
          content_type: content_type,
          filename: filename
        )
        payload[:url] = generated_url

        generated_url
      end
    end
  end
end

Rails.application.config.to_prepare do
  ActiveStorage::DiskController.prepend ActiveStorage::GetParamsFromKey
  ActiveStorage::Service::DiskService.prepend
ActiveStorage::IncludeParamsInKey
end
```

Rafael França

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.