aboutsummaryrefslogtreecommitdiffstats
path: root/actionview
diff options
context:
space:
mode:
authorTimm <kaspth@gmail.com>2013-06-12 15:59:34 +0200
committerTimm <kaspth@gmail.com>2014-06-11 17:53:28 +0200
commitc94e24fbe7bcdf605cafcfabdf97454d1e1e0685 (patch)
treec87092cfabcbb09f8351722b6127dc7d37c87a8f /actionview
parent47baab0c415690aeb71ef3bc4831a462d8ead47b (diff)
downloadrails-c94e24fbe7bcdf605cafcfabdf97454d1e1e0685.tar.gz
rails-c94e24fbe7bcdf605cafcfabdf97454d1e1e0685.tar.bz2
rails-c94e24fbe7bcdf605cafcfabdf97454d1e1e0685.zip
Added Loofah as a dependency in actionview.gemspec.
Implemented ActionView: FullSanitizer, LinkSanitizer and WhiteListSanitizer in sanitizers.rb. Deprecated protocol_separator and bad_tags. Added new tests in sanitizers_test.rb and reimplemented assert_dom_equal with Loofah.
Diffstat (limited to 'actionview')
-rw-r--r--actionview/actionview.gemspec1
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb47
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb116
-rw-r--r--actionview/test/template/sanitizers_test.rb330
4 files changed, 471 insertions, 23 deletions
diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec
index e45dd04225..578e5968e4 100644
--- a/actionview/actionview.gemspec
+++ b/actionview/actionview.gemspec
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
s.add_dependency 'builder', '~> 3.1'
s.add_dependency 'erubis', '~> 2.7.0'
+ s.add_dependency 'loofah', '~> 1.2.1'
s.add_development_dependency 'actionpack', version
s.add_development_dependency 'activemodel', version
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 049af275b6..66894b5936 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/object/try'
-require 'action_view/vendor/html-scanner'
+require 'action_view/helpers/sanitize_helper/sanitizers'
module ActionView
# = Action View Sanitize Helpers
@@ -65,9 +65,9 @@ module ActionView
self.class.white_list_sanitizer.sanitize_css(style)
end
- # Strips all HTML tags from the +html+, including comments. This uses the
- # html-scanner tokenizer and so its HTML parsing ability is limited by
- # that of html-scanner.
+ # Strips all HTML tags from the +html+, including comments. This uses
+ # Nokogiri for tokenization (via Loofah) and so its HTML parsing ability
+ # is limited by that of Nokogiri.
#
# strip_tags("Strip <i>these</i> tags!")
# # => Strip these tags!
@@ -134,11 +134,7 @@ module ActionView
white_list_sanitizer.allowed_protocols
end
- def sanitized_protocol_separator=(value)
- white_list_sanitizer.protocol_separator = value
- end
-
- # Gets the HTML::FullSanitizer instance used by +strip_tags+. Replace with
+ # Gets the ActionView::FullSanitizer instance used by +strip_tags+. Replace with
# any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -146,21 +142,21 @@ module ActionView
# end
#
def full_sanitizer
- @full_sanitizer ||= HTML::FullSanitizer.new
+ @full_sanitizer ||= ActionView::FullSanitizer.new
end
- # Gets the HTML::LinkSanitizer instance used by +strip_links+. Replace with
- # any object that responds to +sanitize+.
+ # Gets the ActionView::LinkSanitizer instance used by +strip_links+.
+ # Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
# config.action_view.link_sanitizer = MySpecialSanitizer.new
# end
#
def link_sanitizer
- @link_sanitizer ||= HTML::LinkSanitizer.new
+ @link_sanitizer ||= ActionView::LinkSanitizer.new
end
- # Gets the HTML::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
+ # Gets the ActionView::WhiteListSanitizer instance used by sanitize and +sanitize_css+.
# Replace with any object that responds to +sanitize+.
#
# class Application < Rails::Application
@@ -168,7 +164,12 @@ module ActionView
# end
#
def white_list_sanitizer
- @white_list_sanitizer ||= HTML::WhiteListSanitizer.new
+ @white_list_sanitizer ||= ActionView::WhiteListSanitizer.new
+ end
+
+
+ def sanitized_protocol_separator=(value)
+ ActionView::WhiteListSanitizer.protocol_separator = value
end
# Adds valid HTML attributes that the +sanitize+ helper checks for URIs.
@@ -178,7 +179,7 @@ module ActionView
# end
#
def sanitized_uri_attributes=(attributes)
- HTML::WhiteListSanitizer.uri_attributes.merge(attributes)
+ ActionView::WhiteListSanitizer.update_uri_attributes(attributes)
end
# Adds to the Set of 'bad' tags for the +sanitize+ helper.
@@ -188,7 +189,7 @@ module ActionView
# end
#
def sanitized_bad_tags=(attributes)
- HTML::WhiteListSanitizer.bad_tags.merge(attributes)
+ ActionView::WhiteListSanitizer.bad_tags = attributes
end
# Adds to the Set of allowed tags for the +sanitize+ helper.
@@ -198,7 +199,7 @@ module ActionView
# end
#
def sanitized_allowed_tags=(attributes)
- HTML::WhiteListSanitizer.allowed_tags.merge(attributes)
+ ActionView::WhiteListSanitizer.update_allowed_tags(attributes)
end
# Adds to the Set of allowed HTML attributes for the +sanitize+ helper.
@@ -208,7 +209,7 @@ module ActionView
# end
#
def sanitized_allowed_attributes=(attributes)
- HTML::WhiteListSanitizer.allowed_attributes.merge(attributes)
+ ActionView::WhiteListSanitizer.update_allowed_attributes(attributes)
end
# Adds to the Set of allowed CSS properties for the #sanitize and +sanitize_css+ helpers.
@@ -218,7 +219,7 @@ module ActionView
# end
#
def sanitized_allowed_css_properties=(attributes)
- HTML::WhiteListSanitizer.allowed_css_properties.merge(attributes)
+ ActionView::WhiteListSanitizer.update_allowed_css_properties(attributes)
end
# Adds to the Set of allowed CSS keywords for the +sanitize+ and +sanitize_css+ helpers.
@@ -228,7 +229,7 @@ module ActionView
# end
#
def sanitized_allowed_css_keywords=(attributes)
- HTML::WhiteListSanitizer.allowed_css_keywords.merge(attributes)
+ ActionView::WhiteListSanitizer.update_allowed_css_keywords(attributes)
end
# Adds to the Set of allowed shorthand CSS properties for the +sanitize+ and +sanitize_css+ helpers.
@@ -238,7 +239,7 @@ module ActionView
# end
#
def sanitized_shorthand_css_properties=(attributes)
- HTML::WhiteListSanitizer.shorthand_css_properties.merge(attributes)
+ ActionView::WhiteListSanitizer.update_shorthand_css_properties(attributes)
end
# Adds to the Set of allowed protocols for the +sanitize+ helper.
@@ -248,7 +249,7 @@ module ActionView
# end
#
def sanitized_allowed_protocols=(attributes)
- HTML::WhiteListSanitizer.allowed_protocols.merge(attributes)
+ ActionView::WhiteListSanitizer.update_allowed_protocols(attributes)
end
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb
new file mode 100644
index 0000000000..74be525581
--- /dev/null
+++ b/actionview/lib/action_view/helpers/sanitize_helper/sanitizers.rb
@@ -0,0 +1,116 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/deprecation'
+require 'loofah'
+
+module ActionView
+
+ class FullSanitizer
+ def sanitize(html, options = {})
+ Loofah.fragment(html).text
+ end
+ end
+
+ class LinkSanitizer
+ def initialize
+ @link_scrubber = Loofah::Scrubber.new do |node|
+ next unless node.name == 'a'
+ node.before node.children
+ node.remove
+ end
+ end
+
+ def sanitize(html, options = {})
+ Loofah.scrub_fragment(html, @link_scrubber).to_s
+ end
+ end
+
+ class WhiteListSanitizer
+ def sanitize(html, options = {})
+ return nil unless html
+ validate_options(options)
+
+ loofah_fragment = Loofah.fragment(html)
+ loofah_fragment.scrub!(:strip)
+ loofah_fragment.xpath("./form").each { |form| form.remove }
+ loofah_fragment.to_s
+ end
+
+ def sanitize_css(style_string)
+ Loofah::HTML5::Scrub.scrub_css style_string
+ end
+
+ def protocol_separator
+ ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.')
+ end
+
+ def protocol_separator=(value)
+ ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.')
+ end
+
+ def bad_tags
+ ActiveSupport::Deprecation.warn('bad_tags has been deprecated and has no effect.')
+ end
+
+ class << self
+ def protocol_separator
+ ActiveSupport::Deprecation.warn('protocol_separator has been deprecated and has no effect.')
+ end
+
+ def protocol_separator=(value)
+ ActiveSupport::Deprecation.warn('protocol_separator= has been deprecated and has no effect.')
+ end
+
+ def bad_tags
+ ActiveSupport::Deprecation.warn('The bad_tags class attribute has been deprecated and has no effect. You can still affect the tags being sanitized using bad_tags= which changes the allowed_tags.')
+ end
+
+ def bad_tags=(tags)
+ allowed_tags.replace(allowed_tags - tags)
+ end
+ end
+
+ [:uri_attributes, :allowed_attributes,
+ :allowed_tags, :allowed_protocols, :allowed_css_properties,
+ :allowed_css_keywords, :shorthand_css_properties].each do |attr|
+ class_attribute attr, :instance_writer => false
+
+ define_method "#{self}.update_#{attr}" do |arg|
+ attr.merge arg
+ end
+ end
+
+ # Constants are from Loofahs source at lib/loofah/html5/whitelist.rb
+ self.uri_attributes = Loofah::HTML5::WhiteList::ATTR_VAL_IS_URI
+
+ self.allowed_tags = Loofah::HTML5::WhiteList::ALLOWED_ELEMENTS
+
+ self.bad_tags = Set.new %w(script)
+
+ self.allowed_attributes = Loofah::HTML5::WhiteList::ALLOWED_ATTRIBUTES
+
+ self.allowed_css_properties = Loofah::HTML5::WhiteList::ALLOWED_CSS_PROPERTIES
+
+ self.allowed_css_keywords = Loofah::HTML5::WhiteList::ALLOWED_CSS_KEYWORDS
+
+ self.shorthand_css_properties = Loofah::HTML5::WhiteList::SHORTHAND_CSS_PROPERTIES
+
+ self.allowed_protocols = Loofah::HTML5::WhiteList::ALLOWED_PROTOCOLS
+
+ protected
+ def validate_options(options)
+ if options[:tags] && !options[:tags].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :tags as an Enumerable"
+ end
+
+ if options[:attributes] && !options[:attributes].is_a?(Enumerable)
+ raise ArgumentError, "You should pass :attributes as an Enumerable"
+ end
+ end
+
+ def contains_bad_protocols?(attr_name, value)
+ protocol_separator = ':'
+ self.uri_attributes.include?(attr_name) &&
+ (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(&#x0*3a)|(%|&#37;)3A/i && !self.allowed_protocols.include?(value.split(protocol_separator).first.downcase.strip))
+ end
+ end
+end
diff --git a/actionview/test/template/sanitizers_test.rb b/actionview/test/template/sanitizers_test.rb
new file mode 100644
index 0000000000..dc2fcf61e8
--- /dev/null
+++ b/actionview/test/template/sanitizers_test.rb
@@ -0,0 +1,330 @@
+require 'abstract_unit'
+
+class SanitizerTest < ActionController::TestCase
+ def setup
+ @sanitizer = nil # used by assert_sanitizer
+ end
+
+ def test_strip_tags_with_quote
+ sanitizer = ActionView::FullSanitizer.new
+ string = '<" <img src="trollface.gif" onload="alert(1)"> hi'
+
+ assert_equal ' hi', sanitizer.sanitize(string)
+ end
+
+ def test_strip_tags
+ sanitizer = ActionView::FullSanitizer.new
+ assert_equal("<<<bad html", sanitizer.sanitize("<<<bad html"))
+ assert_equal("<<", sanitizer.sanitize("<<<bad html>"))
+ assert_equal("Dont touch me", sanitizer.sanitize("Dont touch me"))
+ assert_equal("This is a test.", sanitizer.sanitize("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>"))
+ assert_equal("Weirdos", sanitizer.sanitize("Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos"))
+ assert_equal("This is a test.", sanitizer.sanitize("This is a test."))
+ assert_equal(
+ %{This is a test.\n\n\nIt no longer contains any HTML.\n}, sanitizer.sanitize(
+ %{<title>This is <b>a <a href="" target="_blank">test</a></b>.</title>\n\n<!-- it has a comment -->\n\n<p>It no <b>longer <strong>contains <em>any <strike>HTML</strike></em>.</strong></b></p>\n}))
+ assert_equal "This has a here.", sanitizer.sanitize("This has a <!-- comment --> here.")
+ assert_equal "This has a here.", sanitizer.sanitize("This has a <![CDATA[<section>]]> here.")
+ assert_equal "This has an unclosed ", sanitizer.sanitize("This has an unclosed <![CDATA[<section>]] here...")
+ [nil, '', ' '].each { |blank| assert_equal blank, sanitizer.sanitize(blank) }
+ assert_nothing_raised { sanitizer.sanitize("This is a frozen string with no tags".freeze) }
+ end
+
+ def test_strip_links
+ sanitizer = ActionView::LinkSanitizer.new
+ assert_equal "Dont touch me", sanitizer.sanitize("Dont touch me")
+ assert_equal "on my mind\nall day long", sanitizer.sanitize("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>")
+ assert_equal "0wn3d", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")
+ assert_equal "Magic", sanitizer.sanitize("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")
+ assert_equal "FrrFox", sanitizer.sanitize("<href onlclick='steal()'>FrrFox</a></href>")
+ assert_equal "My mind\nall <b>day</b> long", sanitizer.sanitize("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>")
+ assert_equal "all <b>day</b> long", sanitizer.sanitize("<<a>a href='hello'>all <b>day</b> long<</A>/a>")
+
+ assert_equal "<a<a", sanitizer.sanitize("<a<a")
+ end
+
+ def test_sanitize_form
+ assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", ''
+ end
+
+ def test_sanitize_plaintext
+ raw = "<plaintext><span>foo</span></plaintext>"
+ assert_sanitized raw, "<span>foo</span>"
+ end
+
+ def test_sanitize_script
+ assert_sanitized "a b c<script language=\"Javascript\">blah blah blah</script>d e f", "a b cblah blah blahd e f"
+ end
+
+ def test_sanitize_js_handlers
+ raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>}
+ assert_sanitized raw, %{onthis="do that" <a href="#" name="foo">hello</a>}
+ end
+
+ def test_sanitize_javascript_href
+ raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>}
+ assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>}
+ end
+
+ def test_sanitize_image_src
+ raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>}
+ assert_sanitized raw, %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>}
+ end
+
+ ActionView::WhiteListSanitizer.allowed_tags.each do |tag_name|
+ define_method "test_should_allow_#{tag_name}_tag" do
+ assert_sanitized "start <#{tag_name} title=\"1\" onclick=\"foo\">foo <bad>bar</bad> baz</#{tag_name}> end", %(start <#{tag_name} title="1">foo bar baz</#{tag_name}> end)
+ end
+ end
+
+ def test_should_allow_anchors
+ assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href=\"foo\">baz</a>)
+ end
+
+ # RFC 3986, sec 4.2
+ def test_allow_colons_in_path_component
+ assert_sanitized("<a href=\"./this:that\">foo</a>")
+ end
+
+ %w(src width height alt).each do |img_attr|
+ define_method "test_should_allow_image_#{img_attr}_attribute" do
+ assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo" />)
+ end
+ end
+
+ def test_should_handle_non_html
+ assert_sanitized 'abc'
+ end
+
+ def test_should_handle_blank_text
+ assert_sanitized nil
+ assert_sanitized ''
+ end
+
+ def test_should_allow_custom_tags
+ text = "<u>foo</u>"
+ sanitizer = ActionView::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_only_custom_tags
+ text = "<u>foo</u> with <i>bar</i>"
+ sanitizer = ActionView::WhiteListSanitizer.new
+ assert_equal("<u>foo</u> with bar", sanitizer.sanitize(text, :tags => %w(u)))
+ end
+
+ def test_should_allow_custom_tags_with_attributes
+ text = %(<blockquote cite="http://example.com/">foo</blockquote>)
+ sanitizer = ActionView::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text))
+ end
+
+ def test_should_allow_custom_tags_with_custom_attributes
+ text = %(<blockquote foo="bar">Lorem ipsum</blockquote>)
+ sanitizer = ActionView::WhiteListSanitizer.new
+ assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo']))
+ end
+
+ def test_should_raise_argument_error_if_tags_is_not_enumerable
+ sanitizer = ActionView::WhiteListSanitizer.new
+ e = assert_raise(ArgumentError) do
+ sanitizer.sanitize('', :tags => 'foo')
+ end
+
+ assert_equal "You should pass :tags as an Enumerable", e.message
+ end
+
+ def test_should_raise_argument_error_if_attributes_is_not_enumerable
+ sanitizer = ActionView::WhiteListSanitizer.new
+ e = assert_raise(ArgumentError) do
+ sanitizer.sanitize('', :attributes => 'foo')
+ end
+
+ assert_equal "You should pass :attributes as an Enumerable", e.message
+ end
+
+ [%w(img src), %w(a href)].each do |(tag, attr)|
+ define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do
+ assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>)
+ end
+ end
+
+ def test_should_flag_bad_protocols
+ sanitizer = ActionView::WhiteListSanitizer.new
+ %w(about chrome data disk hcp help javascript livescript lynxcgi lynxexec ms-help ms-its mhtml mocha opera res resource shell vbscript view-source vnd.ms.radio wysiwyg).each do |proto|
+ assert sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://bad")
+ end
+ end
+
+ def test_should_accept_good_protocols_ignoring_case
+ sanitizer = ActionView::WhiteListSanitizer.new
+ ActionView::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', "#{proto.capitalize}://good")
+ end
+ end
+
+ def test_should_accept_good_protocols_ignoring_space
+ sanitizer = ActionView::WhiteListSanitizer.new
+ ActionView::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', " #{proto}://good")
+ end
+ end
+
+ def test_should_accept_good_protocols
+ sanitizer = ActionView::WhiteListSanitizer.new
+ ActionView::WhiteListSanitizer.allowed_protocols.each do |proto|
+ assert !sanitizer.send(:contains_bad_protocols?, 'src', "#{proto}://good")
+ end
+ end
+
+ def test_should_reject_hex_codes_in_protocol
+ assert_sanitized %(<a href="&#37;6A&#37;61&#37;76&#37;61&#37;73&#37;63&#37;72&#37;69&#37;70&#37;74&#37;3A&#37;61&#37;6C&#37;65&#37;72&#37;74&#37;28&#37;22&#37;58&#37;53&#37;53&#37;22&#37;29">1</a>), "<a>1</a>"
+ assert @sanitizer.send(:contains_bad_protocols?, 'src', "%6A%61%76%61%73%63%72%69%70%74%3A%61%6C%65%72%74%28%22%58%53%53%22%29")
+ end
+
+ def test_should_block_script_tag
+ assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), ""
+ end
+
+ [%(<IMG SRC="javascript:alert('XSS');">),
+ %(<IMG SRC=javascript:alert('XSS')>),
+ %(<IMG SRC=JaVaScRiPt:alert('XSS')>),
+ %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">),
+ %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>),
+ %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>),
+ %(<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>),
+ %(<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>),
+ %(<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>),
+ %(<IMG SRC="jav\tascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x09;ascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">),
+ %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">),
+ %(<IMG SRC=" &#14; javascript:alert('XSS');">),
+ %(<IMG SRC="javascript&#x3a;alert('XSS');">),
+ %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i|
+ define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do
+ assert_sanitized img_hack, "<img>"
+ end
+ end
+
+ def test_should_sanitize_tag_broken_up_by_null
+ assert_sanitized %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>), "alert(\"XSS\")"
+ end
+
+ def test_should_sanitize_invalid_script_tag
+ assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), ""
+ end
+
+ def test_should_sanitize_script_tag_with_multiple_open_brackets
+ assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;"
+ assert_sanitized %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a), %(&lt;a)
+ end
+
+ def test_should_sanitize_unclosed_script
+ assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "<b>"
+ end
+
+ def test_should_sanitize_half_open_scripts
+ assert_sanitized %(<IMG SRC="javascript:alert('XSS')"), "<img>"
+ end
+
+ def test_should_not_fall_for_ridiculous_hack
+ img_hack = %(<IMG\nSRC\n=\n"\nj\na\nv\na\ns\nc\nr\ni\np\nt\n:\na\nl\ne\nr\nt\n(\n'\nX\nS\nS\n'\n)\n"\n>)
+ assert_sanitized img_hack, "<img>"
+ end
+
+ def test_should_sanitize_attributes
+ assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="#{CGI.escapeHTML "'><script>alert()</script>"}">blah</span>)
+ end
+
+ def test_should_sanitize_illegal_style_properties
+ raw = %(display:block; position:absolute; left:0; top:0; width:100%; height:100%; z-index:1; background-color:black; background-image:url(http://www.ragingplatypus.com/i/cam-full.jpg); background-x:center; background-y:center; background-repeat:repeat;)
+ expected = %(display: block; width: 100%; height: 100%; background-color: black; background-x: center; background-y: center;)
+ assert_equal expected, sanitize_css(raw)
+ end
+
+ def test_should_sanitize_with_trailing_space
+ raw = "display:block; "
+ expected = "display: block;"
+ assert_equal expected, sanitize_css(raw)
+ end
+
+ def test_should_sanitize_xul_style_attributes
+ raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss'))
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_invalid_tag_names
+ assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f")
+ end
+
+ def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags
+ assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>")
+ end
+
+ def test_should_sanitize_invalid_tag_names_in_single_tags
+ assert_sanitized('<img/src="http://ha.ckers.org/xss.js"/>', "<img />")
+ end
+
+ def test_should_sanitize_img_dynsrc_lowsrc
+ assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img />")
+ end
+
+ def test_should_sanitize_div_background_image_unicode_encoded
+ raw = %(background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029)
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_div_style_expression
+ raw = %(width: expression(alert('XSS'));)
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_across_newlines
+ raw = %(\nwidth:\nexpression(alert('XSS'));\n)
+ assert_equal '', sanitize_css(raw)
+ end
+
+ def test_should_sanitize_img_vbscript
+ assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />'
+ end
+
+ def test_should_sanitize_cdata_section
+ assert_sanitized "<![CDATA[<span>section</span>]]>", "&lt;![CDATA[&lt;span>section&lt;/span>]]>"
+ end
+
+ def test_should_sanitize_unterminated_cdata_section
+ assert_sanitized "<![CDATA[<span>neverending...", "&lt;![CDATA[&lt;span>neverending...]]>"
+ end
+
+ def test_should_not_mangle_urls_with_ampersand
+ assert_sanitized %{<a href=\"http://www.domain.com?var1=1&amp;var2=2\">my link</a>}
+ end
+
+ def test_should_sanitize_neverending_attribute
+ assert_sanitized "<span class=\"\\", "<span class=\"\\\">"
+ end
+
+ def test_x03a
+ assert_sanitized %(<a href="javascript&#x3a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003a;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3a;//legit">), %(<a href="http://legit">)
+ assert_sanitized %(<a href="javascript&#x3A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="javascript&#x003A;alert('XSS');">), "<a>"
+ assert_sanitized %(<a href="http&#x3A;//legit">), %(<a href="http://legit">)
+ end
+
+protected
+ def assert_sanitized(input, expected = nil)
+ @sanitizer ||= ActionView::WhiteListSanitizer.new
+ if input
+ assert_dom_equal expected || input, @sanitizer.sanitize(input)
+ else
+ assert_nil @sanitizer.sanitize(input)
+ end
+ end
+
+ def sanitize_css(input)
+ (@sanitizer ||= ActionView::WhiteListSanitizer.new).sanitize_css(input)
+ end
+end