CVE-2022-45442 (published 2022-11-28T21:15Z)

Sinatra is a domain-specific language for creating web applications in Ruby. An issue was discovered in Sinatra 2.0 before 2.2.3 and 3.0 before 3.0.4. An application is vulnerable to a reflected file download (RFD) attack that sets the Content-Disposition header of a response when the filename is derived from user-supplied input. Version 2.2.3 and 3.0.4 contain patches for this issue.

CVSS v3 base score: 8.8, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-494
cwe_name: Download of Code Without Integrity Check
description: The product downloads source code or an executable from a remote location and executes the code without sufficiently verifying the origin and integrity of the code.
extended_description: An attacker can execute malicious code by compromising the host server, performing DNS spoofing, or modifying the code in transit.
         

Fix information:
repo_url: https://github.com/sinatra/sinatra
commit_details: repo_url: https://github.com/sinatra/sinatra
author: namusyaka
author_date: 2022-11-23 22:24:02+09:00
msg: escape filename in the Content-Disposition header

According the multipart form data spec in WHATWG living standard.
Ref: https://html.spec.whatwg.org/#multipart-form-data
repository: repo_url: https://github.com/sinatra/sinatra
repo_name: sinatra/sinatra
description: Classy web-development dressed in a DSL (official / canonical repo)
repo_language: Ruby
owner: sinatra
file_changes: filename: base.rb
change_type: ModificationType.MODIFY
diff: @@ -396,13 +396,20 @@ module Sinatra
       response['Content-Type'] = mime_type
     end
 
+    # https://html.spec.whatwg.org/#multipart-form-data
+    MULTIPART_FORM_DATA_REPLACEMENT_TABLE = {
+      '"'  => '%22',
+      "\r" => '%0D',
+      "\n" => '%0A'
+    }.freeze
+
     # Set the Content-Disposition to "attachment" with the specified filename,
     # instructing the user agents to prompt to save.
     def attachment(filename = nil, disposition = :attachment)
       response['Content-Disposition'] = disposition.to_s.dup
       return unless filename
 
-      params = format('; filename="%s"', File.basename(filename))
+      params = format('; filename="%s"', File.basename(filename).gsub(/["\r\n]/, MULTIPART_FORM_DATA_REPLACEMENT_TABLE))
       response['Content-Disposition'] << params
       ext = File.extname(filename)
       content_type(ext) unless response['Content-Type'] || ext.empty?

programming_language: Ruby
filename: helpers_test.rb
change_type: ModificationType.MODIFY
diff: @@ -781,6 +781,18 @@ class HelpersTest < Minitest::Test
       assert_equal '<sinatra></sinatra>', body
     end
 
+    it 'escapes filename in the Content-Disposition header according to the multipart form data spec in WHATWG living standard' do
+      mock_app do
+        get('/attachment') do
+          attachment "test.xml\";\r\next=.txt"
+          response.write("<sinatra></sinatra>")
+        end
+      end
+
+      get '/attachment'
+      assert_equal 'attachment; filename="test.xml%22;%0D%0Aext=.txt"', response['Content-Disposition']
+      assert_equal '<sinatra></sinatra>', body
+    end
   end
 
   describe 'send_file' do

programming_language: Ruby