ruby-rack-cors / CVE-2019-18978
Attached is my proposed patch for Jessie. This had to be adapted from
the upstream patch, however I think I have understood the intentions
clearly.
The Rack::Utils.clean_path_info and Rack::Utils.unescape_path functions
required by the upstream patch are not available in Jessie, so I copied
these from upstream:
https://github.com/rack/rack/blob/master/lib/rack/utils.rb
The supplied test case fails as expected without the required change and
succeeds as expected with the required change.
I just noticed I still need to update the changelog entry before
uploading.
diff -Nru ruby-rack-cors-0.2.9/debian/changelog ruby-rack-cors-0.2.9/debian/changelog
--- ruby-rack-cors-0.2.9/debian/changelog 2014-04-24 23:53:38.000000000 +1000
+++ ruby-rack-cors-0.2.9/debian/changelog 2020-02-04 17:25:44.000000000 +1100
@@ -1,3 +1,9 @@
+ruby-rack-cors (0.2.9-1+deb8u1) UNRELEASED; urgency=high
+
+ * Non-maintainer upload by the LTS Team.
+
+ -- Brian May <bam@debian.org> Tue, 04 Feb 2020 17:25:44 +1100
+
ruby-rack-cors (0.2.9-1) unstable; urgency=low
* New upstream release
diff -Nru ruby-rack-cors-0.2.9/debian/patches/CVE-2019-18978.patch ruby-rack-cors-0.2.9/debian/patches/CVE-2019-18978.patch
--- ruby-rack-cors-0.2.9/debian/patches/CVE-2019-18978.patch 1970-01-01 10:00:00.000000000 +1000
+++ ruby-rack-cors-0.2.9/debian/patches/CVE-2019-18978.patch 2020-02-04 17:25:44.000000000 +1100
@@ -0,0 +1,104 @@
+--- a/test/unit/cors_test.rb
++++ b/test/unit/cors_test.rb
+@@ -160,6 +160,12 @@
+ assert_cors_success
+ assert_not_nil last_response.headers['Content-Type']
+ end
++
++ should "decode URL and resolve paths before resource matching" do
++ header 'Origin', 'http://localhost:3000'
++ get '/public/a/..%2F..%2Fprivate/stuff'
++ assert_cors_failure
++ end
+ end
+
+ protected
+--- a/test/unit/test.ru
++++ b/test/unit/test.ru
+@@ -28,6 +28,7 @@
+ allow do
+ origins '*'
+ resource '/public'
++ resource '/public/*'
+ resource '/public_without_credentials', :credentials => false
+ end
+
+--- a/lib/rack/cors.rb
++++ b/lib/rack/cors.rb
+@@ -30,17 +30,20 @@
+ env['HTTP_ORIGIN'] = 'file://' if env['HTTP_ORIGIN'] == 'null'
+ env['HTTP_ORIGIN'] ||= env['HTTP_X_ORIGIN']
+
++ path = evaluate_path(env)
++
+ cors_headers = nil
+ if env['HTTP_ORIGIN']
+ debug(env) do
+ [ 'Incoming Headers:',
+ " Origin: #{env['HTTP_ORIGIN']}",
++ " Path-Info: #{path}",
+ " Access-Control-Request-Method: #{env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']}",
+ " Access-Control-Request-Headers: #{env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"
+ ].join("\n")
+ end
+ if env['REQUEST_METHOD'] == 'OPTIONS' and env['HTTP_ACCESS_CONTROL_REQUEST_METHOD']
+- if headers = process_preflight(env)
++ if headers = process_preflight(env, path)
+ debug(env) do
+ "Preflight Headers:\n" +
+ headers.collect{|kv| " #{kv.join(': ')}"}.join("\n")
+@@ -48,7 +51,7 @@
+ return [200, headers, []]
+ end
+ else
+- cors_headers = process_cors(env)
++ cors_headers = process_cors(env, path)
+ end
+ end
+ status, headers, body = @app.call env
+@@ -74,17 +77,41 @@
+ end
+ end
+
++ PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact)
++
++ def clean_path_info(path_info)
++ parts = path_info.split PATH_SEPS
++
++ clean = []
++
++ parts.each do |part|
++ next if part.empty? || part == '.'
++ part == '..' ? clean.pop : clean << part
++ end
++
++ clean_path = clean.join(::File::SEPARATOR)
++ clean_path.prepend("/") if parts.empty? || parts.first.empty?
++ clean_path
++ end
++
++ def evaluate_path(env)
++ path = env['PATH_INFO']
++# path = Rack::Utils.clean_path_info(Rack::Utils.unescape_path(path)) if path
++ path = clean_path_info(::URI::DEFAULT_PARSER.unescape(path)) if path
++ path
++ end
++
+ def all_resources
+ @all_resources ||= []
+ end
+
+- def process_preflight(env)
+- resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'],env)
++ def process_preflight(env, path)
++ resource = find_resource(env['HTTP_ORIGIN'], path,env)
+ resource && resource.process_preflight(env)
+ end
+
+- def process_cors(env)
+- resource = find_resource(env['HTTP_ORIGIN'], env['PATH_INFO'],env)
++ def process_cors(env, path)
++ resource = find_resource(env['HTTP_ORIGIN'], path,env)
+ resource.to_headers(env) if resource
+ end
+
diff -Nru ruby-rack-cors-0.2.9/debian/patches/series ruby-rack-cors-0.2.9/debian/patches/series
--- ruby-rack-cors-0.2.9/debian/patches/series 1970-01-01 10:00:00.000000000 +1000
+++ ruby-rack-cors-0.2.9/debian/patches/series 2020-02-04 17:20:41.000000000 +1100
@@ -0,0 +1 @@
+CVE-2019-18978.patch
--
Brian May <brian@linuxpenguins.xyz>
https://linuxpenguins.xyz/brian/
Reply to: