From d0c9759d3aeb6327d68dd6c0de0fe2fed4e3c870 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 31 Jul 2012 22:25:54 -0300 Subject: [PATCH] html_escape should escape single quotes https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content Closes #7215 --- .../controller/new_base/render_template_test.rb | 2 +- actionpack/test/controller/render_test.rb | 4 +- actionpack/test/template/asset_tag_helper_test.rb | 16 +++---- actionpack/test/template/erb_util_test.rb | 10 ++-- .../test/template/form_options_helper_test.rb | 6 +-- actionpack/test/template/form_tag_helper_test.rb | 2 +- actionpack/test/template/javascript_helper_test.rb | 10 ++-- actionpack/test/template/template_test.rb | 2 +- actionpack/test/template/url_helper_test.rb | 10 ++-- .../core_ext/string/output_safety.rb | 53 +++++++--------------- activesupport/test/core_ext/string_ext_test.rb | 4 +- railties/test/application/assets_test.rb | 4 +- 12 files changed, 51 insertions(+), 72 deletions(-) diff --git a/actionpack/test/controller/new_base/render_template_test.rb b/actionpack/test/controller/new_base/render_template_test.rb index 584f2d7..04137ea 100644 --- a/actionpack/test/controller/new_base/render_template_test.rb +++ b/actionpack/test/controller/new_base/render_template_test.rb @@ -120,7 +120,7 @@ module RenderTemplate test "rendering a template with error properly exceprts the code" do get :with_error assert_status 500 - assert_match "undefined local variable or method `idontexist'", response.body + assert_match "undefined local variable or method `idontexist", response.body end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index e910c53..f574fd7 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -152,7 +152,7 @@ class TestController < ActionController::Base # :ported: def render_text_hello_world_with_layout - @variable_for_layout = ", I'm here!" + @variable_for_layout = ", I am here!" render :text => "hello world", :layout => true end @@ -781,7 +781,7 @@ class RenderTest < ActionController::TestCase # :ported: def test_do_with_render_text_and_layout get :render_text_hello_world_with_layout - assert_equal "hello world, I'm here!", @response.body + assert_equal "hello world, I am here!", @response.body end # :ported: diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 7900c5a..60f775e 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -162,8 +162,8 @@ class AssetTagHelperTest < ActionView::TestCase %(image_tag(".pdf.png")) => %(.pdf), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(Rails), %(image_tag("//www.rubyonrails.com/images/rails.png")) => %(Rails), - %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(Mouse), - %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(Mouse), + %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(Mouse), + %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(Mouse), %(image_tag("mouse.png", :alt => nil)) => %(), %(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(), } @@ -1096,8 +1096,8 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) assert_dom_equal(%(/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) assert_dom_equal(%(/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) - assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end def test_should_ignore_relative_root_path_on_complete_url @@ -1110,8 +1110,8 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) - assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end def test_should_compute_proper_path_with_asset_host_and_default_protocol @@ -1120,8 +1120,8 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_path("xmlhr")) assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_path("style")) assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_path("xml.png")) - assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) - assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) + assert_dom_equal(%(Mouse), image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) + assert_dom_equal(%(Mouse2), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end def test_should_ignore_asset_host_on_complete_url diff --git a/actionpack/test/template/erb_util_test.rb b/actionpack/test/template/erb_util_test.rb index 790ab1c..e311989 100644 --- a/actionpack/test/template/erb_util_test.rb +++ b/actionpack/test/template/erb_util_test.rb @@ -8,11 +8,11 @@ class ErbUtilTest < Test::Unit::TestCase define_method "test_html_escape_#{expected.gsub(/\W/, '')}" do assert_equal expected, html_escape(given) end + end - unless given == '"' - define_method "test_json_escape_#{expected.gsub(/\W/, '')}" do - assert_equal ERB::Util::JSON_ESCAPE[given], json_escape(given) - end + ERB::Util::JSON_ESCAPE.each do |given, expected| + define_method "test_json_escape_#{expected.gsub(/\W/, '')}" do + assert_equal ERB::Util::JSON_ESCAPE[given], json_escape(given) end end @@ -40,7 +40,7 @@ class ErbUtilTest < Test::Unit::TestCase def test_rest_in_ascii (0..127).to_a.map {|int| int.chr }.each do |chr| - next if chr.in?('&"<>') + next if chr.in?('&"<>\'') assert_equal chr, html_escape(chr) end end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index a557e74..2e29647 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -907,7 +907,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_options_for_select_with_element_attributes assert_dom_equal( - "\n\n\n", + "\n\n\n", options_for_select([ [ "", { :class => 'bold' } ], [ "USA", { :onclick => "alert('Hello World');" } ], [ "Sweden" ], "Germany" ]) ) end @@ -943,13 +943,13 @@ class FormOptionsHelperTest < ActionView::TestCase def test_option_html_attributes_with_multiple_element_hash output = option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ]) assert output.include?(" class=\"fancy\"") - assert output.include?(" onclick=\"alert('Hello World');\"") + assert output.include?(" onclick=\"alert('Hello World');\"") end def test_option_html_attributes_with_multiple_hashes output = option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ]) assert output.include?(" class=\"fancy\"") - assert output.include?(" onclick=\"alert('Hello World');\"") + assert output.include?(" onclick=\"alert('Hello World');\"") end def test_option_html_attributes_with_special_characters diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index f95308b..cb36c70 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -366,7 +366,7 @@ class FormTagHelperTest < ActionView::TestCase def test_submit_tag assert_dom_equal( - %(), + %(), submit_tag("Save", :disable_with => "Saving...", :onclick => "alert('hello!')") ) end diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index dd8b7b7..1467387 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -40,12 +40,12 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_button_to_function - assert_dom_equal %(), + assert_dom_equal %(), button_to_function("Greeting", "alert('Hello world!')") end def test_button_to_function_with_onclick - assert_dom_equal "", + assert_dom_equal "", button_to_function("Greeting", "alert('Hello world!')", :onclick => "alert('Goodbye World :(')") end @@ -55,17 +55,17 @@ class JavaScriptHelperTest < ActionView::TestCase end def test_link_to_function - assert_dom_equal %(Greeting), + assert_dom_equal %(Greeting), link_to_function("Greeting", "alert('Hello world!')") end def test_link_to_function_with_existing_onclick - assert_dom_equal %(Greeting), + assert_dom_equal %(Greeting), link_to_function("Greeting", "alert('Hello world!')", :onclick => "confirm('Sanity!')") end def test_function_with_href - assert_dom_equal %(Greeting), + assert_dom_equal %(Greeting), link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') end diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 13d30a9..efc0b9b 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -77,7 +77,7 @@ class TestERBTemplate < ActiveSupport::TestCase def test_locals @template = new_template("<%= my_local %>") @template.locals = [:my_local] - assert_equal "I'm a local", render(:my_local => "I'm a local") + assert_equal "I am a local", render(:my_local => "I am a local") end def test_restores_buffer diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index d46f5aa..fe4c21b 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -193,7 +193,7 @@ class UrlHelperTest < ActiveSupport::TestCase def test_link_tag_with_custom_onclick link = link_to("Hello", "http://www.example.com", :onclick => "alert('yay!')") - expected = %{Hello} + expected = %{Hello} assert_dom_equal expected, link end @@ -203,12 +203,12 @@ class UrlHelperTest < ActiveSupport::TestCase link_to("Hello", "http://www.example.com", :confirm => "Are you sure?") ) assert_dom_equal( - "Hello", - link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure, can you?") + "Hello", + link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure, can you?") ) assert_dom_equal( - "Hello", - link_to("Hello", "http://www.example.com", :confirm => "You can't possibly be sure,\n can you?") + "Hello", + link_to("Hello", "http://www.example.com", :confirm => "You cant possibly be sure,\n can you?") ) end diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 5c1e025..8a366a1 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -3,45 +3,24 @@ require 'active_support/core_ext/kernel/singleton_class' class ERB module Util - HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' } + HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' } - # Detect whether 1.9 can transcode with XML escaping. - if '"><&""' == ('><&"'.encode('utf-8', :xml => :attr) rescue false) - # A utility method for escaping HTML tag characters. - # This method is also aliased as h. - # - # In your ERB templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> - # - # ==== Example: - # puts html_escape("is a > 0 & a < 10?") - # # => is a > 0 & a < 10? - def html_escape(s) - s = s.to_s - if s.html_safe? - s - else - s.encode(s.encoding, :xml => :attr)[1...-1].html_safe - end - end - else - # A utility method for escaping HTML tag characters. - # This method is also aliased as h. - # - # In your ERB templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> - # - # ==== Example: - # puts html_escape("is a > 0 & a < 10?") - # # => is a > 0 & a < 10? - def html_escape(s) - s = s.to_s - if s.html_safe? - s - else - s.gsub(/[&"><]/n) { |special| HTML_ESCAPE[special] }.html_safe - end + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # In your ERB templates, use this method to escape any unsafe content. For example: + # <%=h @person.name %> + # + # ==== Example: + # puts html_escape("is a > 0 & a < 10?") + # # => is a > 0 & a < 10? + def html_escape(s) + s = s.to_s + if s.html_safe? + s + else + s.gsub(/[&"'><]/n) { |special| HTML_ESCAPE[special] }.html_safe end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index f390a5d..40c4f40 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -464,8 +464,8 @@ class OutputSafetyTest < ActiveSupport::TestCase end test "ERB::Util.html_escape should escape unsafe characters" do - string = '<>&"' - expected = '<>&"' + string = '<>&"\'' + expected = '<>&"'' assert_equal expected, ERB::Util.html_escape(string) end diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb index 4da1dc3..99a9f37 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -232,7 +232,7 @@ module ApplicationTests get '/posts' assert_match(/AssetNotPrecompiledError/, last_response.body) - assert_match(/app.js isn't precompiled/, last_response.body) + assert_match(/app.js isn't precompiled/, last_response.body) end test "assets raise AssetNotPrecompiledError when manifest file is present and requested file isn't precompiled if digest is disabled" do @@ -256,7 +256,7 @@ module ApplicationTests get '/posts' assert_match(/AssetNotPrecompiledError/, last_response.body) - assert_match(/app.js isn't precompiled/, last_response.body) + assert_match(/app.js isn't precompiled/, last_response.body) end test "precompile properly refers files referenced with asset_path and and run in the provided RAILS_ENV" do -- 1.7.11.1