[rubygem-actionpack/f13/master] Fixed CVE-2011-0447

Vít Ondruch vondruch at fedoraproject.org
Thu Feb 24 14:28:59 UTC 2011


commit 1894f3e01c1bcd9c4a1318b087b49bdf79364784
Author: Vít Ondruch <vondruch at redhat.com>
Date:   Thu Feb 24 14:44:30 2011 +0100

    Fixed CVE-2011-0447

 CVE-2011-0447.patch     |  395 +++++++++++++++++++++++++++++++++++++++++++++++
 rubygem-actionpack.spec |    4 +
 2 files changed, 399 insertions(+), 0 deletions(-)
---
diff --git a/CVE-2011-0447.patch b/CVE-2011-0447.patch
new file mode 100644
index 0000000..7bd0911
--- /dev/null
+++ b/CVE-2011-0447.patch
@@ -0,0 +1,395 @@
+From 0fb6347bdc2ca91e012c222d92d59e90716e75ec Mon Sep 17 00:00:00 2001
+From: Michael Koziarski <michael at koziarski.com>
+Date: Mon, 17 Jan 2011 14:12:29 +1300
+Subject: [PATCH 2/2] Change the CSRF whitelisting to only apply to get requests
+
+Unfortunately the previous method of browser detection and XHR whitelisting is unable to prevent requests issued from some Flash animations and Java applets.  To ease the work required to include the CSRF token in ajax requests rails now supports providing the token in a custom http header:
+
+ X-CSRF-Token: ...
+
+This fixes CVE-2011-0447
+---
+ .../request_forgery_protection.rb                  |   15 +-
+ actionpack/lib/action_view/helpers.rb              |    2 +
+ actionpack/lib/action_view/helpers/csrf_helper.rb  |   14 ++
+ .../controller/request_forgery_protection_test.rb  |  216 +++++++++-----------
+ 4 files changed, 117 insertions(+), 130 deletions(-)
+ create mode 100644 actionpack/lib/action_view/helpers/csrf_helper.rb
+
+diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb
+index 24821ff..0030857 100644
+--- a/actionpack/lib/action_controller/request_forgery_protection.rb
++++ b/actionpack/lib/action_controller/request_forgery_protection.rb
+@@ -76,7 +76,11 @@ module ActionController #:nodoc:
+     protected
+       # The actual before_filter that is used.  Modify this to change how you handle unverified requests.
+       def verify_authenticity_token
+-        verified_request? || raise(ActionController::InvalidAuthenticityToken)
++        verified_request? || handle_unverified_request
++      end
++
++      def handle_unverified_request
++        reset_session
+       end
+       
+       # Returns true or false if a request is verified.  Checks:
+@@ -85,11 +89,10 @@ module ActionController #:nodoc:
+       # * is it a GET request?  Gets should be safe and idempotent
+       # * Does the form_authenticity_token match the given token value from the params?
+       def verified_request?
+-        !protect_against_forgery?     ||
+-          request.method == :get      ||
+-          request.xhr?                ||
+-          !verifiable_request_format? ||
+-          form_authenticity_token == form_authenticity_param
++        !protect_against_forgery?                            ||
++          request.get?                                       ||
++          form_authenticity_token == form_authenticity_param ||
++          form_authenticity_token == request.headers['X-CSRF-Token']
+       end
+ 
+       def form_authenticity_param
+diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
+index cea894d..debd2e7 100644
+--- a/actionpack/lib/action_view/helpers.rb
++++ b/actionpack/lib/action_view/helpers.rb
+@@ -6,6 +6,7 @@ module ActionView #:nodoc:
+     autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
+     autoload :CacheHelper, 'action_view/helpers/cache_helper'
+     autoload :CaptureHelper, 'action_view/helpers/capture_helper'
++    autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
+     autoload :DateHelper, 'action_view/helpers/date_helper'
+     autoload :DebugHelper, 'action_view/helpers/debug_helper'
+     autoload :FormHelper, 'action_view/helpers/form_helper'
+@@ -38,6 +39,7 @@ module ActionView #:nodoc:
+     include BenchmarkHelper
+     include CacheHelper
+     include CaptureHelper
++    include CsrfHelper
+     include DateHelper
+     include DebugHelper
+     include FormHelper
+diff --git a/actionpack/lib/action_view/helpers/csrf_helper.rb b/actionpack/lib/action_view/helpers/csrf_helper.rb
+new file mode 100644
+index 0000000..e0e6c9a
+--- /dev/null
++++ b/actionpack/lib/action_view/helpers/csrf_helper.rb
+@@ -0,0 +1,14 @@
++module ActionView
++  # = Action View CSRF Helper
++  module Helpers
++    module CsrfHelper
++      # Returns a meta tag with the cross-site request forgery protection token
++      # for forms to use. Place this in your head.
++      def csrf_meta_tag
++        if protect_against_forgery?
++          %(<meta name="csrf-param" content="#{h(request_forgery_protection_token)}"/>\n<meta name="csrf-token" content="#{h(form_authenticity_token)}"/>)
++        end
++      end
++    end
++  end
++end
+diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
+index c6ad4b9..7502905 100644
+--- a/actionpack/test/controller/request_forgery_protection_test.rb
++++ b/actionpack/test/controller/request_forgery_protection_test.rb
+@@ -23,6 +23,10 @@ module RequestForgeryProtectionActions
+     render :text => 'pwn'
+   end
+ 
++  def meta
++    render :inline => "<%= csrf_meta_tag %>"
++  end
++
+   def rescue_action(e) raise e end
+ end
+ 
+@@ -32,6 +36,16 @@ class RequestForgeryProtectionController < ActionController::Base
+   protect_from_forgery :only => :index
+ end
+ 
++class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base
++  include RequestForgeryProtectionActions
++  protect_from_forgery :only => %w(index meta)
++
++  def handle_unverified_request
++    raise(ActionController::InvalidAuthenticityToken)
++  end
++end
++
++
+ class FreeCookieController < RequestForgeryProtectionController
+   self.allow_forgery_protection = false
+   
+@@ -54,158 +68,92 @@ end
+ # common test methods
+ 
+ module RequestForgeryProtectionTests
+-  def teardown
+-    ActionController::Base.request_forgery_protection_token = nil
+-  end
+-  
++  def setup
++    @token      = "cf50faa3fe97702ca1ae"
+ 
+-  def test_should_render_form_with_token_tag
+-     get :index
+-     assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+-   end
+-
+-   def test_should_render_button_to_with_token_tag
+-     get :show_button
+-     assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+-   end
+-
+-   def test_should_render_remote_form_with_only_one_token_parameter
+-     get :remote_form
+-     assert_equal 1, @response.body.scan(@token).size
+-   end
+-
+-   def test_should_allow_get
+-     get :index
+-     assert_response :success
+-   end
+-
+-   def test_should_allow_post_without_token_on_unsafe_action
+-     post :unsafe
+-     assert_response :success
+-   end
+-
+-  def test_should_not_allow_html_post_without_token
+-    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-    assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html }
++    ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
++    ActionController::Base.request_forgery_protection_token = :authenticity_token
+   end
+   
+-  def test_should_not_allow_html_put_without_token
+-    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-    assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html }
+-  end
+   
+-  def test_should_not_allow_html_delete_without_token
+-    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-    assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html }
+-  end
+-
+-  def test_should_allow_api_formatted_post_without_token
+-    assert_nothing_raised do
+-      post :index, :format => 'xml'
++  def test_should_render_form_with_token_tag
++    assert_not_blocked do
++      get :index
+     end
++    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+   end
+ 
+-  def test_should_not_allow_api_formatted_put_without_token
+-    assert_nothing_raised do
+-      put :index, :format => 'xml'
++  def test_should_render_button_to_with_token_tag
++    assert_not_blocked do
++      get :show_button
+     end
++    assert_select 'form>div>input[name=?][value=?]', 'authenticity_token', @token
+   end
+ 
+-  def test_should_allow_api_formatted_delete_without_token
+-    assert_nothing_raised do
+-      delete :index, :format => 'xml'
+-    end
++  def test_should_allow_get
++    assert_not_blocked { get :index }
+   end
+ 
+-  def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-      post :index, :format => 'xml'
+-    end
++  def test_should_allow_post_without_token_on_unsafe_action
++    assert_not_blocked { post :unsafe }
+   end
+ 
+-  def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-      put :index, :format => 'xml'
+-    end
++  def test_should_not_allow_post_without_token
++    assert_blocked { post :index }
+   end
+ 
+-  def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-      delete :index, :format => 'xml'
+-    end
++  def test_should_not_allow_post_without_token_irrespective_of_format
++    assert_blocked { post :index, :format=>'xml' }
+   end
+ 
+-  def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
+-      post :index, :format => 'xml'
+-    end
++  def test_should_not_allow_put_without_token
++    assert_blocked { put :index }
+   end
+ 
+-  def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
+-      put :index, :format => 'xml'
+-    end
++  def test_should_not_allow_delete_without_token
++    assert_blocked { delete :index }
+   end
+ 
+-  def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token
+-    assert_raise(ActionController::InvalidAuthenticityToken) do
+-      @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s
+-      delete :index, :format => 'xml'
+-    end
+-  end
+-  
+-  def test_should_allow_xhr_post_without_token
+-    assert_nothing_raised { xhr :post, :index }
+-  end
+-  
+-  def test_should_allow_xhr_put_without_token
+-    assert_nothing_raised { xhr :put, :index }
+-  end
+-  
+-  def test_should_allow_xhr_delete_without_token
+-    assert_nothing_raised { xhr :delete, :index }
++  def test_should_not_allow_xhr_post_without_token
++    assert_blocked { xhr :post, :index }
+   end
+-  
+-  def test_should_allow_xhr_post_with_encoded_form_content_type_without_token
+-    @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s
+-    assert_nothing_raised { xhr :post, :index }
+-  end
+-  
++
+   def test_should_allow_post_with_token
+-    post :index, :authenticity_token => @token
+-    assert_response :success
++    assert_not_blocked { post :index, :authenticity_token => @token }
+   end
+   
+   def test_should_allow_put_with_token
+-    put :index, :authenticity_token => @token
+-    assert_response :success
++    assert_not_blocked { put :index, :authenticity_token => @token }
+   end
+   
+   def test_should_allow_delete_with_token
+-    delete :index, :authenticity_token => @token
+-    assert_response :success
++    assert_not_blocked { delete :index, :authenticity_token => @token }
+   end
+   
+-  def test_should_allow_post_with_xml
+-    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
+-    post :index, :format => 'xml'
+-    assert_response :success
++  def test_should_allow_post_with_token_in_header
++    @request.env['HTTP_X_CSRF_TOKEN'] = @token
++    assert_not_blocked { post :index }
++  end
++
++  def test_should_allow_delete_with_token_in_header
++    @request.env['HTTP_X_CSRF_TOKEN'] = @token
++    assert_not_blocked { delete :index }
+   end
+   
+-  def test_should_allow_put_with_xml
+-    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
+-    put :index, :format => 'xml'
++  def test_should_allow_put_with_token_in_header
++    @request.env['HTTP_X_CSRF_TOKEN'] = @token
++    assert_not_blocked { put :index }
++  end
++
++  def assert_blocked
++    session[:something_like_user_id] = 1
++    yield
++    assert_nil session[:something_like_user_id], "session values are still present"
+     assert_response :success
+   end
+   
+-  def test_should_allow_delete_with_xml
+-    @request.env['CONTENT_TYPE'] = Mime::XML.to_s
+-    delete :index, :format => 'xml'
++  def assert_not_blocked
++    assert_nothing_raised { yield }
+     assert_response :success
+   end
+ end
+@@ -214,15 +162,20 @@ end
+ 
+ class RequestForgeryProtectionControllerTest < ActionController::TestCase
+   include RequestForgeryProtectionTests
+-  def setup
+-    @controller = RequestForgeryProtectionController.new
+-    @request    = ActionController::TestRequest.new
+-    @request.format = :html
+-    @response   = ActionController::TestResponse.new
+-    @token      = "cf50faa3fe97702ca1ae"
+ 
+-    ActiveSupport::SecureRandom.stubs(:base64).returns(@token)
+-    ActionController::Base.request_forgery_protection_token = :authenticity_token
++  test 'should emit a csrf-token meta tag' do
++    ActiveSupport::SecureRandom.stubs(:base64).returns(@token + '<=?')
++    get :meta
++    assert_equal %(<meta name="csrf-param" content="authenticity_token"/>\n<meta name="csrf-token" content="cf50faa3fe97702ca1ae&lt;=?"/>), @response.body
++  end
++end
++
++class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase
++  include RequestForgeryProtectionTests
++  def assert_blocked
++    assert_raises(ActionController::InvalidAuthenticityToken) do
++      yield
++    end
+   end
+ end
+ 
+@@ -251,15 +204,30 @@ class FreeCookieControllerTest < ActionController::TestCase
+       assert_nothing_raised { send(method, :index)}
+     end
+   end
++
++  test 'should not emit a csrf-token meta tag' do
++    get :meta
++    assert @response.body.blank?, "#{@response.body.inspect} is not blank"
++  end
+ end
+ 
++
++
++
++
+ class CustomAuthenticityParamControllerTest < ActionController::TestCase
+   def setup
++    ActionController::Base.request_forgery_protection_token = :custom_token_name
++    super
++  end
++
++  def teardown
+     ActionController::Base.request_forgery_protection_token = :authenticity_token
++    super
+   end
+ 
+   def test_should_allow_custom_token
+-    post :index, :authenticity_token => 'foobar'
++    post :index, :custom_token_name => 'foobar'
+     assert_response :ok
+   end
+ end
+-- 
+1.7.2
+
diff --git a/rubygem-actionpack.spec b/rubygem-actionpack.spec
index 25198ad..e2696f2 100644
--- a/rubygem-actionpack.spec
+++ b/rubygem-actionpack.spec
@@ -22,6 +22,8 @@ Patch0:  rubygem-actionpack-2.3.4-enable-test.patch
 Patch2:  rubygem-actionpack-2.3.5-rack-compat.patch
 # http://groups.google.com/group/rubyonrails-security/browse_thread/thread/f02a48ede8315f81
 Patch3:  CVE-2011-0446.patch
+# http://groups.google.com/group/rubyonrails-security/t/2d95a3cc23e03665
+Patch4:  CVE-2011-0447.patch
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 Requires: rubygems
 Requires: rubygem(activesupport) = %{version}
@@ -57,6 +59,7 @@ pushd .%{geminstdir}
 %patch0 -p0
 %patch2 -p0
 %patch3 -p2
+%patch4 -p2
 
 # create missing symlink
 pushd test/fixtures/layout_tests/layouts/
@@ -129,6 +132,7 @@ rake test --trace
 %changelog
 * Thu Feb 24 2011 Vít Ondruch <vondruch at redhat.com> - 1:2.3.5-4
 - Fixed CVE-2011-0446
+- Fixed CVE-2011-0447
 
 * Wed Sep 15 2010 Mohammed Morsi <mmorsi at redhat.com> - 1:2.3.5-3
 - additional rack compat fix (in patch2)


More information about the scm-commits mailing list