Ruby on Rails 'implicit render' functionality Directory Traversal Vulnerability (CVE-2014-0130)

2017-04-28T00:00:00
ID SSV:93066
Type seebug
Reporter Root
Modified 2017-04-28T00:00:00

Description

Impact

The implicit render functionality allows controllers to render a template, even if there is no explicit action with the corresponding name. This module does not perform adequate input sanitization which could allow an attacker to use a specially crafted request to retrieve arbitrary files from the rails application server.

In order to be vulnerable an application must specifically use globbing routes[1] in combination with the :action parameter. The purpose of the route globbing feature is to allow parameters to contain characters which would otherwise be regarded as separators, for example '/' and '.'. As these characters have semantic meaning within template filenames, it is highly unlikely that applications are deliberately combining these functions.

To determine if you are vulnerable, search your application's routes files for '*action' and if you find any, use one of the work arounds below.

Releases

The 4.1.1, 4.0.5 and 3.2.18 releases are available at the normal locations.

Workarounds

The simplest workaround is to simply not use globbing matches for the :action parameter. As action methods cannot contain a '/' character, the simple matching should be sufficient. So replace

get 'my_url/*action', controller: 'asdf'

with

get 'my_url/:action', controller: 'asdf'

If your application depends on this functionality, you will need to rename the route parameter and add an explicit action:

get 'my_url/*template_path', controller: 'asdf', action: 'display'

Then add an action which renders explicitly:

def display if !params[:template_path].index('.') render file: params[:template_path] end end

Note: The path check in this example may not be suitable for your application, take care

Patches

To aid users who aren't able to upgrade immediately we have provided patches for the two supported release series. They are in git-am format and consist of a single changeset.

  • 4-1-directory_traversal.patch - Patch for 4.1 series
  • 4-0-directory_traversal.patch - Patch for 4.0 series
  • 3-2-directory_traversal.patch - Patch for 3.2 series

Please note that only the 4.1.x, 4.0.x and 3.2.x series are supported at present. Users of earlier unsupported releases are advised to upgrade as soon as possible as we cannot guarantee the continued availability of security fixes for unsupported releases.

Credits

Thanks to Ville Lautanala of Flowdock for reporting the vulnerability to us, and working with us on a fix.

[1] http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments

[ CONTENT OF TYPE text/html SKIPPED ]

>From 0f3b7d1a319383f743f9938e1eed00f0fba7a367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafaelmfranca@...il.com> Date: Thu, 17 Apr 2014 16:50:39 -0300 Subject: [PATCH] Only accept actions without File::SEPARATOR in the name.

This will avoid directory traversal in implicit render.

Fixes: CVE-2014-0130

``` actionpack/lib/abstract_controller/base.rb | 28 +++++++++++++++++++--- .../new_base/render_implicit_action_test.rb | 17 ++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index fd6a46f..2541125 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -112,7 +112,7 @@ module AbstractController def process(action, *args) @_action_name = action_name = action.to_s

  • unless action_name = method_for_action(action_name)
  • unless action_name = _find_action_name(action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end

@@ -138,7 +138,7 @@ module AbstractController # available action consider actions that are also available # through other means, for example, implicit render ones. def available_action?(action_name) - method_for_action(action_name).present? + _find_action_name(action_name).present? end

 private

@@ -182,6 +182,23 @@ module AbstractController end

   # Takes an action name and returns the name of the method that will
  • handle the action.

  • It checks if the action name is valid and returns false otherwise.

  • See method_for_action for more information.

  • ==== Parameters

  • * <tt>action_name</tt> - An action name to find a method name for

  • ==== Returns

  • * <tt>string</tt> - The name of the method that handles the action

  • * false - No valid method name could be found. Raise ActionNotFound.

  • def _find_action_name(action_name)
  • _valid_action_name?(action_name) && method_for_action(action_name)
  • end +
  • Takes an action name and returns the name of the method that will

    # handle the action. In normal cases, this method returns the same # name as it receives. By default, if #method_for_action receives # a name that is not an action, it will look for an #action_missing @@ -203,11 +220,16 @@ module AbstractController # # ==== Returns # * <tt>string</tt> - The name of the method that handles the action

  • * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.

  • * <tt>nil</tt> - No method name could be found.

    def method_for_action(action_name) if action_method?(action_name) then action_name elsif respond_to?(:action_missing, true) then "_handle_action_missing" end end +

  • Checks if the action name is valid and returns false otherwise.

  • def _valid_action_name?(action_name)
  • action_name.to_s !~ Regexp.new(File::SEPARATOR)
  • end end end ```

``` diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 1e2191d..5b4885f 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -6,7 +6,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" - )] + ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', FILE))]

 def hello_world() end

end @@ -33,10 +33,25 @@ module RenderImplicitAction assert_status 200 end

  • test "render does not traverse the file system" do
  • assert_raises(AbstractController::ActionNotFound) do
  • action_name = %w(.. .. fixtures shared).join(File::SEPARATOR)
  • SimpleController.action(action_name).call(Rack::MockRequest.env_for("/"))
  • end
  • end + test "available_action? returns true for implicit actions" do assert SimpleController.new.available_action?(:hello_world) assert SimpleController.new.available_action?(:"hyphen-ated") assert SimpleController.new.available_action?(:not_implemented) end +
  • test "available_action? does not allow File::SEPARATOR on the name" do
  • action_name = %w(evil .. .. path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym) +
  • action_name = %w(evil path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
  • end end end ```

1.9.1

>From a8ecbfde294a2ec53f034e729d663cb266dc9e62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafaelmfranca@...il.com> Date: Thu, 17 Apr 2014 16:50:39 -0300 Subject: [PATCH] Only accept actions without File::SEPARATOR in the name.

This will avoid directory traversal in implicit render.

Fixes: CVE-2014-0130

``` actionpack/lib/abstract_controller/base.rb | 28 +++++++++++++++++++--- .../new_base/render_implicit_action_test.rb | 17 ++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index af5de81..26f8160 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -127,7 +127,7 @@ module AbstractController def process(action, *args) @_action_name = action_name = action.to_s

  • unless action_name = method_for_action(action_name)
  • unless action_name = _find_action_name(action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end

@@ -160,7 +160,7 @@ module AbstractController # ==== Returns # * <tt>TrueClass</tt>, <tt>FalseClass</tt> def available_action?(action_name) - method_for_action(action_name).present? + _find_action_name(action_name).present? end

 private

@@ -204,6 +204,23 @@ module AbstractController end

   # Takes an action name and returns the name of the method that will
  • handle the action.

  • It checks if the action name is valid and returns false otherwise.

  • See method_for_action for more information.

  • ==== Parameters

  • * <tt>action_name</tt> - An action name to find a method name for

  • ==== Returns

  • * <tt>string</tt> - The name of the method that handles the action

  • * false - No valid method name could be found. Raise ActionNotFound.

  • def _find_action_name(action_name)
  • _valid_action_name?(action_name) && method_for_action(action_name)
  • end +
  • Takes an action name and returns the name of the method that will

    # handle the action. In normal cases, this method returns the same # name as it receives. By default, if #method_for_action receives # a name that is not an action, it will look for an #action_missing @@ -225,7 +242,7 @@ module AbstractController # # ==== Returns # * <tt>string</tt> - The name of the method that handles the action

  • * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.

  • * <tt>nil</tt> - No method name could be found.

    def method_for_action(action_name) if action_method?(action_name) action_name @@ -233,5 +250,10 @@ module AbstractController "_handle_action_missing" end end +

  • Checks if the action name is valid and returns false otherwise.

  • def _valid_action_name?(action_name)
  • action_name.to_s !~ Regexp.new(File::SEPARATOR)
  • end end end ```

``` diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 1e2191d..5b4885f 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -6,7 +6,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented" - )] + ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', FILE))]

 def hello_world() end

end @@ -33,10 +33,25 @@ module RenderImplicitAction assert_status 200 end

  • test "render does not traverse the file system" do
  • assert_raises(AbstractController::ActionNotFound) do
  • action_name = %w(.. .. fixtures shared).join(File::SEPARATOR)
  • SimpleController.action(action_name).call(Rack::MockRequest.env_for("/"))
  • end
  • end + test "available_action? returns true for implicit actions" do assert SimpleController.new.available_action?(:hello_world) assert SimpleController.new.available_action?(:"hyphen-ated") assert SimpleController.new.available_action?(:not_implemented) end +
  • test "available_action? does not allow File::SEPARATOR on the name" do
  • action_name = %w(evil .. .. path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym) +
  • action_name = %w(evil path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
  • end end end ``` 1.9.1

>From 529720df0193991c66350154e8bed5a59dbaacfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= <rafaelmfranca@...il.com> Date: Thu, 17 Apr 2014 16:50:39 -0300 Subject: [PATCH] Only accept actions without File::SEPARATOR in the name.

This will avoid directory traversal in implicit render.

Fixes: CVE-2014-0130

``` actionpack/lib/abstract_controller/base.rb | 28 +++++++++++++++++++--- .../new_base/render_implicit_action_test.rb | 17 ++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index af5de81..26f8160 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -127,7 +127,7 @@ module AbstractController def process(action, *args) @_action_name = action_name = action.to_s

  • unless action_name = method_for_action(action_name)
  • unless action_name = _find_action_name(action_name) raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" end

@@ -160,7 +160,7 @@ module AbstractController # ==== Returns # * <tt>TrueClass</tt>, <tt>FalseClass</tt> def available_action?(action_name) - method_for_action(action_name).present? + _find_action_name(action_name).present? end

 private

@@ -204,6 +204,23 @@ module AbstractController end

   # Takes an action name and returns the name of the method that will
  • handle the action.

  • It checks if the action name is valid and returns false otherwise.

  • See method_for_action for more information.

  • ==== Parameters

  • * <tt>action_name</tt> - An action name to find a method name for

  • ==== Returns

  • * <tt>string</tt> - The name of the method that handles the action

  • * false - No valid method name could be found. Raise ActionNotFound.

  • def _find_action_name(action_name)
  • _valid_action_name?(action_name) && method_for_action(action_name)
  • end +
  • Takes an action name and returns the name of the method that will

    # handle the action. In normal cases, this method returns the same # name as it receives. By default, if #method_for_action receives # a name that is not an action, it will look for an #action_missing @@ -225,7 +242,7 @@ module AbstractController # # ==== Returns # * <tt>string</tt> - The name of the method that handles the action

  • * <tt>nil</tt> - No method name could be found. Raise ActionNotFound.

  • * <tt>nil</tt> - No method name could be found.

    def method_for_action(action_name) if action_method?(action_name) action_name @@ -233,5 +250,10 @@ module AbstractController "_handle_action_missing" end end +

  • Checks if the action name is valid and returns false otherwise.

  • def _valid_action_name?(action_name)
  • action_name.to_s !~ Regexp.new(File::SEPARATOR)
  • end end end diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb index 1e2191d..5b4885f 100644 --- a/actionpack/test/controller/new_base/render_implicit_action_test.rb +++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb @@ -6,7 +6,7 @@ module RenderImplicitAction "render_implicit_action/simple/hello_world.html.erb" => "Hello world!", "render_implicit_action/simple/hyphen-ated.html.erb" => "Hello hyphen-ated!", "render_implicit_action/simple/not_implemented.html.erb" => "Not Implemented"
  • )]
  • ), ActionView::FileSystemResolver.new(File.expand_path('../../../controller', FILE))]

    def hello_world() end end @@ -33,10 +33,25 @@ module RenderImplicitAction assert_status 200 end

  • test "render does not traverse the file system" do

  • assert_raises(AbstractController::ActionNotFound) do
  • action_name = %w(.. .. fixtures shared).join(File::SEPARATOR)
  • SimpleController.action(action_name).call(Rack::MockRequest.env_for("/"))
  • end
  • end + test "available_action? returns true for implicit actions" do assert SimpleController.new.available_action?(:hello_world) assert SimpleController.new.available_action?(:"hyphen-ated") assert SimpleController.new.available_action?(:not_implemented) end +
  • test "available_action? does not allow File::SEPARATOR on the name" do
  • action_name = %w(evil .. .. path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym) +
  • action_name = %w(evil path).join(File::SEPARATOR)
  • assert_equal false, SimpleController.new.available_action?(action_name.to_sym)
  • end end end ```
                                        
                                            
                                                参考PoC 链接:https://github.com/omarkurt/cve-2014-0130
Payload : site/api/%5C../%5C../%5C../%5C../%5C../{{CAT_DIR}}