Discussion:
[oss-security] [CVE-2018-16477] Bypass vulnerability in Active Storage
Rafael Mendonça França
2018-11-27 21:11:36 UTC
Permalink
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

Loading...