activestorage 5.2.1 → 5.2.1.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activestorage might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e3cb1f60951d79219c1e3b4424ffd1c4b50678c70a24dca0c1a4a015ea468802
4
- data.tar.gz: 830ffc76e9876db10f3e7dcba276b20c41c5d83ed8afbd210334811cf71ddbf8
3
+ metadata.gz: 91213c28f24de81bd5f9fb70fc824cebab2a78b4a9a7e7181dab0073241e10df
4
+ data.tar.gz: be7321827bdee9da52fb288c8625c1d06c13efcea0e7e38115294cc7c87f1629
5
5
  SHA512:
6
- metadata.gz: e94e32e5bf798b0d4313ae66d7cbb60c381997d76980e387e671c6977ee2738f31b6cfc8f37f9ccf1e1f455d7b6261455359811ed1877d5a49214d22f1dca1cb
7
- data.tar.gz: cef3b437d62a555be6bbc2681c81429099661108042dcc1030a3af51a9590165487c78fadffdec0481f7b2559b68dc53f4a2a034b13e8868cd4efe34d0fcc9c5
6
+ metadata.gz: 020ebe643211ff9093027c6ec35a0626e565779a0f0d9a27723cb452b1c66cf9c1b10df89aec84cf068ef1d24ddb90ef8b844054eaaa497bc41b59e42a0f0514
7
+ data.tar.gz: 9c3e51fbe837f60c86eb993d9ba1d85b66362a298fb432629f1dca0b79bdbd500c86fb6640d99d023636d4f8094867367b7cb2bd5d2b6616fe93fa9a8dea6a13
@@ -1,3 +1,12 @@
1
+ ## Rails 5.2.1.1 (November 27, 2018) ##
2
+
3
+ * Prevent content type and disposition bypass in storage service URLs.
4
+
5
+ Fix CVE-2018-16477.
6
+
7
+ *Rosa Gutierrez*
8
+
9
+
1
10
  ## Rails 5.2.1 (August 07, 2018) ##
2
11
 
3
12
  * Fix direct upload with zero-byte files.
@@ -11,10 +11,10 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
11
11
 
12
12
  def show
13
13
  if key = decode_verified_key
14
- response.headers["Content-Type"] = params[:content_type] || DEFAULT_SEND_FILE_TYPE
15
- response.headers["Content-Disposition"] = params[:disposition] || DEFAULT_SEND_FILE_DISPOSITION
14
+ response.headers["Content-Type"] = key[:content_type] || DEFAULT_SEND_FILE_TYPE
15
+ response.headers["Content-Disposition"] = key[:disposition] || DEFAULT_SEND_FILE_DISPOSITION
16
16
 
17
- disk_service.download key do |chunk|
17
+ disk_service.download key[:key] do |chunk|
18
18
  response.stream.write chunk
19
19
  end
20
20
  else
@@ -120,8 +120,8 @@ class ActiveStorage::Blob < ActiveRecord::Base
120
120
  def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options)
121
121
  filename = ActiveStorage::Filename.wrap(filename || self.filename)
122
122
 
123
- service.url key, expires_in: expires_in, filename: filename, content_type: content_type,
124
- disposition: forcibly_serve_as_binary? ? :attachment : disposition, **options
123
+ service.url key, expires_in: expires_in, filename: filename, content_type: content_type_for_service_url,
124
+ disposition: forced_disposition_for_service_url || disposition, **options
125
125
  end
126
126
 
127
127
  # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
@@ -152,7 +152,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
152
152
  self.byte_size = io.size
153
153
  self.identified = true
154
154
 
155
- service.upload(key, io, checksum: checksum)
155
+ service.upload key, io, checksum: checksum, **service_metadata
156
156
  end
157
157
 
158
158
  # Downloads the file associated with this blob. If no block is given, the entire file is read into memory and returned.
@@ -203,5 +203,29 @@ class ActiveStorage::Blob < ActiveRecord::Base
203
203
  ActiveStorage.content_types_to_serve_as_binary.include?(content_type)
204
204
  end
205
205
 
206
+ def allowed_inline?
207
+ ActiveStorage.content_types_allowed_inline.include?(content_type)
208
+ end
209
+
210
+ def content_type_for_service_url
211
+ forcibly_serve_as_binary? ? ActiveStorage.binary_content_type : content_type
212
+ end
213
+
214
+ def forced_disposition_for_service_url
215
+ if forcibly_serve_as_binary? || !allowed_inline?
216
+ :attachment
217
+ end
218
+ end
219
+
220
+ def service_metadata
221
+ if forcibly_serve_as_binary?
222
+ { content_type: ActiveStorage.binary_content_type, disposition: :attachment, filename: filename }
223
+ elsif !allowed_inline?
224
+ { content_type: content_type, disposition: :attachment, filename: filename }
225
+ else
226
+ { content_type: content_type }
227
+ end
228
+ end
229
+
206
230
  ActiveSupport.run_load_hooks(:active_storage_blob, self)
207
231
  end
@@ -2,7 +2,10 @@
2
2
 
3
3
  module ActiveStorage::Blob::Identifiable
4
4
  def identify
5
- update! content_type: identify_content_type, identified: true unless identified?
5
+ unless identified?
6
+ update! content_type: identify_content_type, identified: true
7
+ update_service_metadata
8
+ end
6
9
  end
7
10
 
8
11
  def identified?
@@ -17,4 +20,8 @@ module ActiveStorage::Blob::Identifiable
17
20
  def download_identifiable_chunk
18
21
  service.download_chunk key, 0...4.kilobytes
19
22
  end
23
+
24
+ def update_service_metadata
25
+ service.update_metadata key, service_metadata if service_metadata.any?
26
+ end
20
27
  end
@@ -48,4 +48,6 @@ module ActiveStorage
48
48
  mattr_accessor :paths, default: {}
49
49
  mattr_accessor :variable_content_types, default: []
50
50
  mattr_accessor :content_types_to_serve_as_binary, default: []
51
+ mattr_accessor :content_types_allowed_inline, default: []
52
+ mattr_accessor :binary_content_type, default: "application/octet-stream"
51
53
  end
@@ -37,6 +37,18 @@ module ActiveStorage
37
37
  text/xml
38
38
  application/xml
39
39
  application/xhtml+xml
40
+ application/mathml+xml
41
+ text/cache-manifest
42
+ )
43
+
44
+ config.active_storage.content_types_allowed_inline = %w(
45
+ image/png
46
+ image/gif
47
+ image/jpg
48
+ image/jpeg
49
+ image/vnd.adobe.photoshop
50
+ image/vnd.microsoft.icon
51
+ application/pdf
40
52
  )
41
53
 
42
54
  config.eager_load_namespaces << ActiveStorage
@@ -51,6 +63,8 @@ module ActiveStorage
51
63
 
52
64
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
53
65
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
66
+ ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
67
+ ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
54
68
  end
55
69
  end
56
70
 
@@ -10,7 +10,7 @@ module ActiveStorage
10
10
  MAJOR = 5
11
11
  MINOR = 2
12
12
  TINY = 1
13
- PRE = nil
13
+ PRE = "1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -64,10 +64,16 @@ module ActiveStorage
64
64
 
65
65
  # Upload the +io+ to the +key+ specified. If a +checksum+ is provided, the service will
66
66
  # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError.
67
- def upload(key, io, checksum: nil)
67
+ def upload(key, io, checksum: nil, **options)
68
68
  raise NotImplementedError
69
69
  end
70
70
 
71
+ # Update metadata for the file identified by +key+ in the service.
72
+ # Override in subclasses only if the service needs to store specific
73
+ # metadata that has to be updated upon identification.
74
+ def update_metadata(key, **metadata)
75
+ end
76
+
71
77
  # Return the content of the file at the +key+.
72
78
  def download(key)
73
79
  raise NotImplementedError
@@ -17,7 +17,7 @@ module ActiveStorage
17
17
  @container = container
18
18
  end
19
19
 
20
- def upload(key, io, checksum: nil)
20
+ def upload(key, io, checksum: nil, **)
21
21
  instrument :upload, key: key, checksum: checksum do
22
22
  begin
23
23
  blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum)
@@ -15,7 +15,7 @@ module ActiveStorage
15
15
  @root = root
16
16
  end
17
17
 
18
- def upload(key, io, checksum: nil)
18
+ def upload(key, io, checksum: nil, **)
19
19
  instrument :upload, key: key, checksum: checksum do
20
20
  IO.copy_stream(io, make_path_for(key))
21
21
  ensure_integrity_of(key, checksum) if checksum
@@ -75,17 +75,23 @@ module ActiveStorage
75
75
 
76
76
  def url(key, expires_in:, filename:, disposition:, content_type:)
77
77
  instrument :url, key: key do |payload|
78
- verified_key_with_expiration = ActiveStorage.verifier.generate(key, expires_in: expires_in, purpose: :blob_key)
79
-
80
- generated_url =
81
- url_helpers.rails_disk_service_url(
82
- verified_key_with_expiration,
83
- host: current_host,
84
- filename: filename,
85
- disposition: content_disposition_with(type: disposition, filename: filename),
78
+ content_disposition = content_disposition_with(type: disposition, filename: filename)
79
+ verified_key_with_expiration = ActiveStorage.verifier.generate(
80
+ {
81
+ key: key,
82
+ disposition: content_disposition,
86
83
  content_type: content_type
87
- )
84
+ },
85
+ { expires_in: expires_in,
86
+ purpose: :blob_key }
87
+ )
88
88
 
89
+ generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration,
90
+ host: current_host,
91
+ disposition: content_disposition,
92
+ content_type: content_type,
93
+ filename: filename
94
+ )
89
95
  payload[:url] = generated_url
90
96
 
91
97
  generated_url
@@ -15,22 +15,30 @@ module ActiveStorage
15
15
  @config = config
16
16
  end
17
17
 
18
- def upload(key, io, checksum: nil)
18
+ def upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil)
19
19
  instrument :upload, key: key, checksum: checksum do
20
20
  begin
21
- # The official GCS client library doesn't allow us to create a file with no Content-Type metadata.
22
- # We need the file we create to have no Content-Type so we can control it via the response-content-type
23
- # param in signed URLs. Workaround: let the GCS client create the file with an inferred
24
- # Content-Type (usually "application/octet-stream") then clear it.
25
- bucket.create_file(io, key, md5: checksum).update do |file|
26
- file.content_type = nil
27
- end
21
+ # GCS's signed URLs don't include params such as response-content-type response-content_disposition
22
+ # in the signature, which means an attacker can modify them and bypass our effort to force these to
23
+ # binary and attachment when the file's content type requires it. The only way to force them is to
24
+ # store them as object's metadata.
25
+ content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
26
+ bucket.create_file(io, key, md5: checksum, content_type: content_type, content_disposition: content_disposition)
28
27
  rescue Google::Cloud::InvalidArgumentError
29
28
  raise ActiveStorage::IntegrityError
30
29
  end
31
30
  end
32
31
  end
33
32
 
33
+ def update_metadata(key, content_type:, disposition: nil, filename: nil)
34
+ instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do
35
+ file_for(key).update do |file|
36
+ file.content_type = content_type
37
+ file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
38
+ end
39
+ end
40
+ end
41
+
34
42
  # FIXME: Download in chunks when given a block.
35
43
  def download(key)
36
44
  instrument :download, key: key do
@@ -24,9 +24,9 @@ module ActiveStorage
24
24
 
25
25
  # Upload the +io+ to the +key+ specified to all services. If a +checksum+ is provided, all services will
26
26
  # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError.
27
- def upload(key, io, checksum: nil)
27
+ def upload(key, io, checksum: nil, **options)
28
28
  each_service.collect do |service|
29
- service.upload key, io.tap(&:rewind), checksum: checksum
29
+ service.upload key, io.tap(&:rewind), checksum: checksum, **options
30
30
  end
31
31
  end
32
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.1
4
+ version: 5.2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-07 00:00:00.000000000 Z
11
+ date: 2018-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 5.2.1
19
+ version: 5.2.1.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 5.2.1
26
+ version: 5.2.1.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activerecord
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 5.2.1
33
+ version: 5.2.1.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 5.2.1
40
+ version: 5.2.1.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: marcel
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -124,8 +124,8 @@ homepage: http://rubyonrails.org
124
124
  licenses:
125
125
  - MIT
126
126
  metadata:
127
- source_code_uri: https://github.com/rails/rails/tree/v5.2.1/activestorage
128
- changelog_uri: https://github.com/rails/rails/blob/v5.2.1/activestorage/CHANGELOG.md
127
+ source_code_uri: https://github.com/rails/rails/tree/v5.2.1.1/activestorage
128
+ changelog_uri: https://github.com/rails/rails/blob/v5.2.1.1/activestorage/CHANGELOG.md
129
129
  post_install_message:
130
130
  rdoc_options: []
131
131
  require_paths: