Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:30717
HistoryMay 10, 2014 - 12:00 a.m.

[oss-security] [CVE-2014-0130] Directory Traversal Vulnerability With Certain Route Configurations

2014-05-1000:00:00
vulners.com
76

There is a vulnerability in the 'implicit render' functionality in Ruby on Rails. This vulnerability has been assigned the CVE identifier CVE-2014-0130.

Versions Affected: All Supported
Not affected: None
Fixed Versions: 4.1.1, 4.0.5, 3.2.18

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

3-2-directory_traversal.patch

From 0f3b7d1a319383f743f9938e1eed00f0fba7a367 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?=
<[email protected]>
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&#40;action_name&#41;
    
  •  unless action_name = _find_action_name&#40;action_name&#41;
       raise ActionNotFound, &quot;The action &#39;#{action}&#39; could not be found for #{self.class.name}&quot;
     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&#40;action_name&#41;.present?
    
  •  _find_action_name&#40;action_name&#41;.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
    
  •  # * &lt;tt&gt;action_name&lt;/tt&gt; - An action name to find a method name for
    
  •  #
    
  •  # ==== Returns
    
  •  # * &lt;tt&gt;string&lt;/tt&gt; - The name of the method that handles the action
    
  •  # * false           - No valid method name could be found. Raise ActionNotFound.
    
  •  def _find_action_name&#40;action_name&#41;
    
  •    _valid_action_name?&#40;action_name&#41; &amp;&amp; method_for_action&#40;action_name&#41;
    
  •  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

  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found. Raise ActionNotFound.
    
  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found.
     def method_for_action&#40;action_name&#41;
       if action_method?&#40;action_name&#41; then action_name
       elsif respond_to?&#40;:action_missing, true&#41; then &quot;_handle_action_missing&quot;
       end
     end
    
  •  # Checks if the action name is valid and returns false otherwise.
    
  •  def _valid_action_name?&#40;action_name&#41;
    
  •    action_name.to_s !~ Regexp.new&#40;File::SEPARATOR&#41;
    
  •  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&#40;AbstractController::ActionNotFound&#41; do
    
  •    action_name = &#37;w&#40;.. .. fixtures shared&#41;.join&#40;File::SEPARATOR&#41;
    
  •    SimpleController.action&#40;action_name&#41;.call&#40;Rack::MockRequest.env_for&#40;&quot;/&quot;&#41;&#41;
    
  •  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 = &#37;w&#40;evil .. .. path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  •  action_name = &#37;w&#40;evil path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  • end
    end
    end
    – 1.9.1

4-0-directory_traversal.patch

From a8ecbfde294a2ec53f034e729d663cb266dc9e62 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?=
<[email protected]>
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&#40;action_name&#41;
    
  •  unless action_name = _find_action_name&#40;action_name&#41;
       raise ActionNotFound, &quot;The action &#39;#{action}&#39; could not be found for #{self.class.name}&quot;
     end
    

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

  •  method_for_action&#40;action_name&#41;.present?
    
  •  _find_action_name&#40;action_name&#41;.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
    
  •  # * &lt;tt&gt;action_name&lt;/tt&gt; - An action name to find a method name for
    
  •  #
    
  •  # ==== Returns
    
  •  # * &lt;tt&gt;string&lt;/tt&gt; - The name of the method that handles the action
    
  •  # * false           - No valid method name could be found. Raise ActionNotFound.
    
  •  def _find_action_name&#40;action_name&#41;
    
  •    _valid_action_name?&#40;action_name&#41; &amp;&amp; method_for_action&#40;action_name&#41;
    
  •  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

  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found. Raise ActionNotFound.
    
  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found.
     def method_for_action&#40;action_name&#41;
       if action_method?&#40;action_name&#41;
         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?&#40;action_name&#41;
    
  •    action_name.to_s !~ Regexp.new&#40;File::SEPARATOR&#41;
    
  •  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&#40;AbstractController::ActionNotFound&#41; do
    
  •    action_name = &#37;w&#40;.. .. fixtures shared&#41;.join&#40;File::SEPARATOR&#41;
    
  •    SimpleController.action&#40;action_name&#41;.call&#40;Rack::MockRequest.env_for&#40;&quot;/&quot;&#41;&#41;
    
  •  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 = &#37;w&#40;evil .. .. path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  •  action_name = &#37;w&#40;evil path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  • end
    end
    end
    – 1.9.1

4-1-directory_traversal.patch

From 529720df0193991c66350154e8bed5a59dbaacfe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?=
<[email protected]>
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&#40;action_name&#41;
    
  •  unless action_name = _find_action_name&#40;action_name&#41;
       raise ActionNotFound, &quot;The action &#39;#{action}&#39; could not be found for #{self.class.name}&quot;
     end
    

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

  •  method_for_action&#40;action_name&#41;.present?
    
  •  _find_action_name&#40;action_name&#41;.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
    
  •  # * &lt;tt&gt;action_name&lt;/tt&gt; - An action name to find a method name for
    
  •  #
    
  •  # ==== Returns
    
  •  # * &lt;tt&gt;string&lt;/tt&gt; - The name of the method that handles the action
    
  •  # * false           - No valid method name could be found. Raise ActionNotFound.
    
  •  def _find_action_name&#40;action_name&#41;
    
  •    _valid_action_name?&#40;action_name&#41; &amp;&amp; method_for_action&#40;action_name&#41;
    
  •  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

  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found. Raise ActionNotFound.
    
  •  # * &lt;tt&gt;nil&lt;/tt&gt;    - No method name could be found.
     def method_for_action&#40;action_name&#41;
       if action_method?&#40;action_name&#41;
         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?&#40;action_name&#41;
    
  •    action_name.to_s !~ Regexp.new&#40;File::SEPARATOR&#41;
    
  •  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&#40;AbstractController::ActionNotFound&#41; do
    
  •    action_name = &#37;w&#40;.. .. fixtures shared&#41;.join&#40;File::SEPARATOR&#41;
    
  •    SimpleController.action&#40;action_name&#41;.call&#40;Rack::MockRequest.env_for&#40;&quot;/&quot;&#41;&#41;
    
  •  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 = &#37;w&#40;evil .. .. path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  •  action_name = &#37;w&#40;evil path&#41;.join&#40;File::SEPARATOR&#41;
    
  •  assert_equal false, SimpleController.new.available_action?&#40;action_name.to_sym&#41;
    
  • end
    end
    end
    – 1.9.1