CVE-2011-2929: Improper Input Validation (CWE-20)
value: The template selection functionality in actionpack/lib/action_view/template/resolver.rb in Ruby on Rails 3.0.x before 3.0.10 and 3.1.x before 3.1.0.rc6 does not properly handle glob characters, which allows remote attackers to render arbitrary views via a crafted URL, related to a "filter skipping vulnerability."
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Published: 2011-08-29T18:55Z
Repository: https://github.com/rails/rails
Language: Ruby
Files changed: actionpack/lib/action_view/template/resolver.rb, actionpack/test/controller/render_test.rb, actionpack/test/fixtures/test/hello_w*rld.erb
Commit date: 2011-08-16 15:28:27 -0700
Commit message: Properly escape glob characters.

Diff: diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 7abaa07bc..e78d3a82b 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -140,20 +140,28 @@ def query(path, details, formats)
     end
 
     # Helper for building query glob string based on resolver's pattern.
     def build_query(path, details)
       query = @pattern.dup
-      query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
-      query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
+
+      prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
+      query.gsub!(/\:prefix(\/)?/, prefix)
+
+      partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
+      query.gsub!(/\:action/, partial)
 
       details.each do |ext, variants|
         query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
       end
 
       File.expand_path(query, @path)
     end
 
+    def escape_entry(entry)
+      entry.gsub(/(\*|\[|\]|\{|\}|\?)/, "\\\\\\1")
+    end
+
     # Returns the file mtime from the filesystem.
     def mtime(p)
       File.mtime(p)
     end
 
@@ -226,12 +234,13 @@ def eql?(resolver)
 
   # An Optimized resolver for Rails' most common case.
   class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
     def build_query(path, details)
       exts = EXTENSIONS.map { |ext| details[ext] }
+      query = escape_entry(File.join(@path, path))
 
-      File.join(@path, path) + exts.map { |ext|
+      query + exts.map { |ext|
         "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}"
       }.join
     end
   end
 
diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb
index ce4b407c7..6bcd606bf 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -403,10 +403,18 @@ def accessing_params_in_template_with_layout
   # :ported:
   def render_with_explicit_template
     render :template => "test/hello_world"
   end
 
+  def render_with_explicit_unescaped_template
+    render :template => "test/h*llo_world"
+  end
+
+  def render_with_explicit_escaped_template
+    render :template => "test/hello_w*rld"
+  end
+
   def render_with_explicit_string_template
     render "test/hello_world"
   end
 
   # :ported:
@@ -1055,10 +1063,16 @@ def test_accessing_params_in_template_with_layout
   def test_render_with_explicit_template
     get :render_with_explicit_template
     assert_response :success
   end
 
+  def test_render_with_explicit_unescaped_template
+    assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template }
+    get :render_with_explicit_escaped_template
+    assert_equal "Hello w*rld!", @response.body
+  end
+
   def test_render_with_explicit_string_template
     get :render_with_explicit_string_template
     assert_equal "<html>Hello world!</html>", @response.body
   end
 
diff --git a/actionpack/test/fixtures/test/hello_w*rld.erb b/actionpack/test/fixtures/test/hello_w*rld.erb
new file mode 100644
index 000000000..bc8fa5e0c
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello_w*rld.erb
@@ -0,0 +1 @@
+Hello w*rld!
\ No newline at end of file

Vulnerable code:       query.gsub!(/\:prefix(\/)?/, path.prefix.empty? ? "" : "#{path.prefix}\\1") # prefix can be empty...
      query.gsub!(/\:action/, path.partial? ? "_#{path.name}" : path.name)
      File.join(@path, path) + exts.map { |ext|
Fixed code: 
      prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
      query.gsub!(/\:prefix(\/)?/, prefix)

      partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
      query.gsub!(/\:action/, partial)
    def escape_entry(entry)
      entry.gsub(/(\*|\[|\]|\{|\}|\?)/, "\\\\\\1")
    end

      query = escape_entry(File.join(@path, path))
      query + exts.map { |ext|
  def render_with_explicit_unescaped_template
    render :template => "test/h*llo_world"
  end

  def render_with_explicit_escaped_template
    render :template => "test/hello_w*rld"
  end

  def test_render_with_explicit_unescaped_template
    assert_raise(ActionView::MissingTemplate) { get :render_with_explicit_unescaped_template }
    get :render_with_explicit_escaped_template
    assert_equal "Hello w*rld!", @response.body
  end

Hello w*rld!