Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 7589

Show
Ignore:
Timestamp:
09/23/07 00:11:08 (8 months ago)
Author:
rick
Message:

Secure #sanitize, #strip_tags, and #strip_links helpers against xss attacks. Closes #8877. [Rick, lifofifo, Jacques Distler]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r7571 r7589  
    11*SVN* 
     2 
     3* Secure #sanitize, #strip_tags, and #strip_links helpers against xss attacks.  Closes #8877. [Rick, lifofifo, Jacques Distler] 
     4 
     5  This merges and renames the popular white_list helper (along with some css sanitizing from Jacques Distler version of the same plugin). 
     6  Also applied updated versions of #strip_tags and #strip_links from #8877. 
    27 
    38* Remove use of & logic operator. Closes #8114. [watson] 
  • trunk/actionpack/lib/action_view/base.rb

    r7513 r7589  
    199199    @@erb_variable = '_erbout' 
    200200    cattr_accessor :erb_variable 
     201     
     202    # A regular expression of the valid characters used to separate protocols like 
     203    # the ':' in 'http://foo.com' 
     204    @@sanitized_protocol_separator = /:|(&#0*58)|(&#x70)|(%|%)3A/ 
     205    cattr_accessor :sanitized_protocol_separator 
     206 
     207    # Specifies a Set of HTML attributes that can have URIs. 
     208    @@sanitized_uri_attributes = Set.new(%w(href src cite action longdesc xlink:href lowsrc)) 
     209    cattr_reader :sanitized_uri_attributes 
     210 
     211    # Adds valid HTML attributes that the #sanitize helper checks for URIs. 
     212    # 
     213    #   Rails::Initializer.run do |config| 
     214    #     config.action_view.sanitized_uri_attributes = 'lowsrc', 'target' 
     215    #   end 
     216    # 
     217    def self.sanitized_uri_attributes=(attributes) 
     218      @@sanitized_uri_attributes.merge(attributes) 
     219    end 
     220 
     221    # Specifies a Set of 'bad' tags that the #sanitize helper will remove completely, as opposed 
     222    # to just escaping harmless tags like <font> 
     223    @@sanitized_bad_tags = Set.new('script') 
     224    cattr_reader :sanitized_bad_tags 
     225     
     226    # Adds to the Set of 'bad' tags for the #sanitize helper. 
     227    # 
     228    #   Rails::Initializer.run do |config| 
     229    #     config.action_view.sanitized_bad_tags = 'embed', 'object' 
     230    #   end 
     231    # 
     232    def self.sanitized_bad_tags=(attributes) 
     233      @@sanitized_bad_tags.merge(attributes) 
     234    end 
     235     
     236    # Specifies the default Set of tags that the #sanitize helper will allow unscathed. 
     237    @@sanitized_allowed_tags = Set.new(%w(strong em b i p code pre tt output samp kbd var sub  
     238      sup dfn cite big small address hr br div span h1 h2 h3 h4 h5 h6 ul ol li dt dd abbr  
     239      acronym a img blockquote del ins fieldset legend)) 
     240    cattr_reader :sanitized_allowed_tags 
     241 
     242    # Adds to the Set of allowed tags for the #sanitize helper. 
     243    # 
     244    #   Rails::Initializer.run do |config| 
     245    #     config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' 
     246    #   end 
     247    # 
     248    def self.sanitized_allowed_tags=(attributes) 
     249      @@sanitized_allowed_tags.merge(attributes) 
     250    end 
     251 
     252    # Specifies the default Set of html attributes that the #sanitize helper will leave  
     253    # in the allowed tag. 
     254    @@sanitized_allowed_attributes = Set.new(%w(href src width height alt cite datetime title class name xml:lang abbr)) 
     255    cattr_reader :sanitized_allowed_attributes 
     256 
     257    # Adds to the Set of allowed html attributes for the #sanitize helper. 
     258    # 
     259    #   Rails::Initializer.run do |config| 
     260    #     config.action_view.sanitized_allowed_attributes = 'onclick', 'longdesc' 
     261    #   end 
     262    # 
     263    def self.sanitized_allowed_attributes=(attributes) 
     264      @@sanitized_allowed_attributes.merge(attributes) 
     265    end 
     266 
     267    # Specifies the default Set of acceptable css properties that #sanitize and #sanitize_css will accept. 
     268    @@sanitized_allowed_css_properties = Set.new(%w(azimuth background-color border-bottom-color border-collapse  
     269      border-color border-left-color border-right-color border-top-color clear color cursor direction display  
     270      elevation float font font-family font-size font-style font-variant font-weight height letter-spacing line-height 
     271      overflow pause pause-after pause-before pitch pitch-range richness speak speak-header speak-numeral speak-punctuation 
     272      speech-rate stress text-align text-decoration text-indent unicode-bidi vertical-align voice-family volume white-space 
     273      width)) 
     274    cattr_reader :sanitized_allowed_css_properties 
     275 
     276    # Adds to the Set of allowed css properties for the #sanitize and #sanitize_css heleprs. 
     277    # 
     278    #   Rails::Initializer.run do |config| 
     279    #     config.action_view.sanitized_allowed_css_properties = 'expression' 
     280    #   end 
     281    # 
     282    def self.sanitized_allowed_css_properties=(attributes) 
     283      @@sanitized_allowed_css_properties.merge(attributes) 
     284    end 
     285     
     286    # Specifies the default Set of acceptable css keywords that #sanitize and #sanitize_css will accept. 
     287    @@sanitized_allowed_css_keywords = Set.new(%w(auto aqua black block blue bold both bottom brown center 
     288      collapse dashed dotted fuchsia gray green !important italic left lime maroon medium none navy normal 
     289      nowrap olive pointer purple red right solid silver teal top transparent underline white yellow)) 
     290    cattr_reader :sanitized_allowed_css_keywords 
     291 
     292    # Adds to the Set of allowed css keywords for the #sanitize and #sanitize_css helpers. 
     293    # 
     294    #   Rails::Initializer.run do |config| 
     295    #     config.action_view.sanitized_allowed_css_keywords = 'expression' 
     296    #   end 
     297    # 
     298    def self.sanitized_allowed_css_keywords=(attributes) 
     299      @@sanitized_allowed_css_keywords.merge(attributes) 
     300    end 
     301     
     302    # Specifies the default Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers. 
     303    @@sanitized_shorthand_css_properties = Set.new(%w(background border margin padding)) 
     304    cattr_reader :sanitized_shorthand_css_properties 
     305 
     306    # Adds to the Set of allowed shorthand css properties for the #sanitize and #sanitize_css helpers. 
     307    # 
     308    #   Rails::Initializer.run do |config| 
     309    #     config.action_view.sanitized_shorthand_css_properties = 'expression' 
     310    #   end 
     311    # 
     312    def self.sanitized_shorthand_css_properties=(attributes) 
     313      @@sanitized_shorthand_css_properties.merge(attributes) 
     314    end 
     315 
     316    # Specifies the default Set of protocols that the #sanitize helper will leave in 
     317    # protocol attributes. 
     318    @@sanitized_allowed_protocols = Set.new(%w(ed2k ftp http https irc mailto news gopher nntp telnet webcal xmpp callto feed svn urn aim rsync tag ssh sftp rtsp afs)) 
     319    cattr_reader :sanitized_allowed_protocols 
     320 
     321    # Adds to the Set of allowed protocols for the #sanitize helper. 
     322    # 
     323    #   Rails::Initializer.run do |config| 
     324    #     config.action_view.sanitized_allowed_protocols = 'ssh', 'feed' 
     325    #   end 
     326    # 
     327    def self.sanitized_allowed_protocols=(attributes) 
     328      @@sanitized_allowed_protocols.merge(attributes) 
     329    end 
    201330 
    202331    @@template_handlers = HashWithIndifferentAccess.new 
  • trunk/actionpack/lib/action_view/helpers/text_helper.rb

    r7562 r7589  
    325325      #   strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.') 
    326326      #   # => Blog: Visit 
    327       def strip_links(text) 
    328         text.gsub(/<a\b.*?>(.*?)<\/a>/mi, '\1') 
    329       end 
    330  
    331       VERBOTEN_TAGS = %w(form script plaintext) unless defined?(VERBOTEN_TAGS) 
    332       VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS) 
    333  
    334       # Sanitizes the +html+ by converting <form> and <script> tags into regular 
    335       # text, and removing all "on*" (e.g., onClick) attributes so that arbitrary Javascript 
    336       # cannot be executed. It also removes <tt>href</tt> and <tt>src</tt> attributes that start with 
    337       # "javascript:". You can modify what gets sanitized by defining VERBOTEN_TAGS 
    338       # and VERBOTEN_ATTRS before this Module is loaded. 
    339       # 
    340       # ==== Examples 
    341       #   sanitize('<script> do_nasty_stuff() </script>') 
    342       #   # => &lt;script> do_nasty_stuff() &lt;/script> 
    343       # 
    344       #   sanitize('<a href="javascript: sucker();">Click here for $100</a>') 
    345       #   # => <a>Click here for $100</a> 
    346       # 
    347       #   sanitize('<a href="#" onClick="kill_all_humans();">Click here!!!</a>') 
    348       #   # => <a href="#">Click here!!!</a> 
    349       # 
    350       #   sanitize('<img src="javascript:suckers_run_this();" />') 
    351       #   # => <img /> 
    352       def sanitize(html) 
    353         # only do this if absolutely necessary 
    354         if html.index("<") 
     327      def strip_links(html) 
     328        # Stupid firefox treats '<href="http://whatever.com" onClick="alert()">something' as link!  
     329        if html.index("<a") || html.index("<href")    
     330          tokenizer = HTML::Tokenizer.new(html)  
     331          result = '' 
     332          while token = tokenizer.next  
     333            node = HTML::Node.parse(nil, 0, 0, token, false)  
     334            result << node.to_s unless node.is_a?(HTML::Tag) && ["a", "href"].include?(node.name)  
     335          end  
     336          strip_links(result) # Recurse - handle all dirty nested links 
     337        else 
     338          html 
     339        end 
     340      end 
     341 
     342      # This #sanitize helper will html encode all tags and strip all attributes that aren't specifically allowed.   
     343      # It also strips href/src tags with invalid protocols, like javascript: especially.  It does its best to counter any 
     344      # tricks that hackers may use, like throwing in unicode/ascii/hex values to get past the javascript: filters.  Check out 
     345      # the extensive test suite. 
     346      # 
     347      #   <%= sanitize @article.body %> 
     348      #  
     349      # You can add or remove tags/attributes if you want to customize it a bit.  See ActionView::Base for full docs on the 
     350      # available options.  You can add tags/attributes for single uses of #sanitize by passing either the :attributes or :tags options: 
     351      # 
     352      # Normal Use 
     353      # 
     354      #   <%= sanitize @article.body %> 
     355      # 
     356      # Custom Use 
     357      # 
     358      #   <%= sanitize @article.body, :tags => %w(table tr td), :attributes => %w(id class style) 
     359      #  
     360      # Add table tags 
     361      #    
     362      #   Rails::Initializer.run do |config| 
     363      #     config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td' 
     364      #   end 
     365      #  
     366      # Remove tags 
     367      #    
     368      #   Rails::Initializer.run do |config| 
     369      #     config.after_initialize do 
     370      #       ActionView::Base.sanitized_allowed_tags.delete 'div' 
     371      #     end 
     372      #   end 
     373      #  
     374      # Change allowed attributes 
     375      #  
     376      #   Rails::Initializer.run do |config| 
     377      #     config.action_view.sanitized_allowed_attributes = 'id', 'class', 'style' 
     378      #   end 
     379      #  
     380      def sanitize(html, options = {}) 
     381        return html if html.blank? || !html.include?('<') 
     382        attrs = options.key?(:attributes) ? Set.new(options[:attributes]).merge(sanitized_allowed_attributes) : sanitized_allowed_attributes 
     383        tags  = options.key?(:tags)       ? Set.new(options[:tags]      ).merge(sanitized_allowed_tags)       : sanitized_allowed_tags 
     384        returning [] do |new_text| 
    355385          tokenizer = HTML::Tokenizer.new(html) 
    356           new_text = "" 
    357  
     386          parent    = []  
    358387          while token = tokenizer.next 
    359388            node = HTML::Node.parse(nil, 0, 0, token, false) 
    360389            new_text << case node 
    361390              when HTML::Tag 
    362                 if VERBOTEN_TAGS.include?(node.name) 
    363                   node.to_s.gsub(/</, "&lt;") 
     391                if node.closing == :close 
     392                  parent.shift 
    364393                else 
    365                   if node.closing != :close 
    366                     node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS } 
    367                     %w(href src).each do |attr| 
    368                       node.attributes.delete attr if node.attributes[attr] =~ /^javascript:/i 
    369                     end 
     394                  parent.unshift node.name 
     395                end 
     396                node.attributes.keys.each do |attr_name| 
     397                  value = node.attributes[attr_name].to_s 
     398                  if !attrs.include?(attr_name) || contains_bad_protocols?(attr_name, value) 
     399                    node.attributes.delete(attr_name) 
     400                  else 
     401                    node.attributes[attr_name] = attr_name == 'style' ? sanitize_css(value) : CGI::escapeHTML(value) 
    370402                  end 
    371                   node.to_
    372                 end 
     403                end if node.attribute
     404                tags.include?(node.name) ? node : nil 
    373405              else 
    374                 node.to_s.gsub(/</, "&lt;") 
     406                sanitized_bad_tags.include?(parent.first) ? nil : node.to_s.gsub(/</, "&lt;") 
    375407            end 
    376408          end 
    377  
    378           html = new_text 
    379         end 
    380  
    381         html 
    382       end 
    383        
     409        end.join 
     410      end 
     411 
     412      # Sanitizes a block of css code.  Used by #sanitize when it comes across a style attribute 
     413      def sanitize_css(style) 
     414        # disallow urls 
     415        style = style.to_s.gsub(/url\s*\(\s*[^\s)]+?\s*\)\s*/, ' ') 
     416 
     417        # gauntlet 
     418        if style !~ /^([:,;#%.\sa-zA-Z0-9!]|\w-\w|\'[\s\w]+\'|\"[\s\w]+\"|\([\d,\s]+\))*$/ || 
     419            style !~ /^(\s*[-\w]+\s*:\s*[^:;]*(;|$))*$/ 
     420          return '' 
     421        end 
     422 
     423        returning [] do |clean| 
     424          style.scan(/([-\w]+)\s*:\s*([^:;]*)/) do |prop,val| 
     425            if sanitized_allowed_css_properties.include?(prop.downcase) 
     426              clean <<  prop + ': ' + val + ';' 
     427            elsif sanitized_shorthand_css_properties.include?(prop.split('-')[0].downcase)  
     428              unless val.split().any? do |keyword| 
     429                !sanitized_allowed_css_keywords.include?(keyword) &&  
     430                  keyword !~ /^(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)$/ 
     431              end 
     432                clean << prop + ': ' + val + ';' 
     433              end 
     434            end 
     435          end 
     436        end.join(' ') 
     437      end 
     438 
    384439      # Strips all HTML tags from the +html+, including comments.  This uses the  
    385440      # html-scanner tokenizer and so its HTML parsing ability is limited by  
     
    408463          # strip any comments, and if they have a newline at the end (ie. line with 
    409464          # only a comment) strip that too 
    410           text.gsub(/<!--(.*?)-->[\n]?/m, "")  
     465          strip_tags(text.gsub(/<!--(.*?)-->[\n]?/m, "")) # Recurse - handle all dirty nested tags 
    411466        else 
    412467          html # already plain text 
     
    575630          end 
    576631        end 
     632 
     633        def contains_bad_protocols?(attr_name, value) 
     634          sanitized_uri_attributes.include?(attr_name) &&  
     635          (value =~ /(^[^\/:]*):|(&#0*58)|(&#x70)|(%|&#37;)3A/ && !sanitized_allowed_protocols.include?(value.split(sanitized_protocol_separator).first)) 
     636        end 
    577637    end 
    578638  end 
  • trunk/actionpack/test/template/text_helper_test.rb

    r7562 r7589  
    66  include ActionView::Helpers::TagHelper 
    77  include TestingSandbox 
    8    
     8 
    99  def setup 
    1010    # This simulates the fact that instance variables are reset every time 
     
    4848   
    4949  def test_strip_links 
     50    assert_equal "Dont touch me", strip_links("Dont touch me") 
    5051    assert_equal "on my mind\nall day long", strip_links("<a href='almost'>on my mind</a>\n<A href='almost'>all day long</A>") 
     52    assert_equal "0wn3d", strip_links("<a href='http://www.rubyonrails.com/'><a href='http://www.rubyonrails.com/' onlclick='steal()'>0wn3d</a></a>")  
     53    assert_equal "Magic", strip_links("<a href='http://www.rubyonrails.com/'>Mag<a href='http://www.ruby-lang.org/'>ic")  
     54    assert_equal "FrrFox", strip_links("<href onlclick='steal()'>FrrFox</a></href>")  
     55    assert_equal "My mind\nall <b>day</b> long", strip_links("<a href='almost'>My mind</a>\n<A href='almost'>all <b>day</b> long</A>") 
     56    assert_equal "all <b>day</b> long", strip_links("<<a>a href='hello'>all <b>day</b> long<</A>/a>") 
    5157  end 
    5258 
     
    256262 
    257263  def test_sanitize_form 
    258     raw = "<form action=\"/foo/bar\" method=\"post\"><input></form>" 
    259     result = sanitize(raw) 
    260     assert_equal %(&lt;form action="/foo/bar" method="post"><input>&lt;/form>), result 
     264    assert_sanitized "<form action=\"/foo/bar\" method=\"post\"><input></form>", '' 
    261265  end 
    262266 
    263267  def test_sanitize_plaintext 
    264268    raw = "<plaintext><span>foo</span></plaintext>" 
    265     result = sanitize(raw) 
    266     assert_equal "&lt;plaintext><span>foo</span>&lt;/plaintext>", result 
     269    assert_sanitized raw, "<span>foo</span>" 
    267270  end 
    268271 
    269272  def test_sanitize_script 
    270     raw = "<script language=\"Javascript\">blah blah blah</script>" 
    271     result = sanitize(raw) 
    272     assert_equal %{&lt;script language="Javascript">blah blah blah&lt;/script>}, result 
     273    raw = "a b c<script language=\"Javascript\">blah blah blah</script>d e f" 
     274    assert_sanitized raw, "a b cd e f" 
    273275  end 
    274276 
    275277  def test_sanitize_js_handlers 
    276278    raw = %{onthis="do that" <a href="#" onclick="hello" name="foo" onbogus="remove me">hello</a>} 
    277     result = sanitize(raw) 
    278     assert_equal %{onthis="do that" <a name="foo" href="#">hello</a>}, result 
     279    assert_sanitized raw, %{onthis="do that" <a name="foo" href="#">hello</a>} 
    279280  end 
    280281 
    281282  def test_sanitize_javascript_href 
    282283    raw = %{href="javascript:bang" <a href="javascript:bang" name="hello">foo</a>, <span href="javascript:bang">bar</span>} 
    283     result = sanitize(raw) 
    284     assert_equal %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>}, result 
     284    assert_sanitized raw, %{href="javascript:bang" <a name="hello">foo</a>, <span>bar</span>} 
    285285  end 
    286286   
    287287  def test_sanitize_image_src 
    288288    raw = %{src="javascript:bang" <img src="javascript:bang" width="5">foo</img>, <span src="javascript:bang">bar</span>} 
    289     result = sanitize(raw) 
    290     assert_equal %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>}, result 
    291   end 
    292    
     289    assert_sanitized raw, %{src="javascript:bang" <img width="5">foo</img>, <span>bar</span>} 
     290  end 
     291 
     292  ActionView::Base.sanitized_allowed_tags.each do |tag_name| 
     293    define_method "test_should_allow_#{tag_name}_tag" do 
     294      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) 
     295    end 
     296  end 
     297 
     298  def test_should_allow_anchors 
     299    assert_sanitized %(<a href="foo" onclick="bar"><script>baz</script></a>), %(<a href="foo"></a>) 
     300  end 
     301 
     302  # RFC 3986, sec 4.2 
     303  def test_allow_colons_in_path_component 
     304    assert_sanitized("<a href=\"./this:that\">foo</a>") 
     305  end 
     306 
     307  %w(src width height alt).each do |img_attr| 
     308    define_method "test_should_allow_image_#{img_attr}_attribute" do 
     309      assert_sanitized %(<img #{img_attr}="foo" onclick="bar" />), %(<img #{img_attr}="foo" />) 
     310    end 
     311  end 
     312 
     313  def test_should_handle_non_html 
     314    assert_sanitized 'abc' 
     315  end 
     316 
     317  def test_should_handle_blank_text 
     318    assert_sanitized nil 
     319    assert_sanitized '' 
     320  end 
     321 
     322  def test_should_allow_custom_tags 
     323    text = "<u>foo</u>" 
     324    assert_equal(text, sanitize(text, :tags => %w(u))) 
     325  end 
     326 
     327  def test_should_allow_custom_tags_with_attributes 
     328    text = %(<fieldset foo="bar">foo</fieldset>) 
     329    assert_equal(text, sanitize(text, :attributes => ['foo'])) 
     330  end 
     331 
     332  [%w(img src), %w(a href)].each do |(tag, attr)| 
     333    define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do 
     334      assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>) 
     335    end 
     336  end 
     337 
     338  def test_should_flag_bad_protocols 
     339    %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| 
     340      assert contains_bad_protocols?('src', "#{proto}://bad") 
     341    end 
     342  end 
     343 
     344  def test_should_accept_good_protocols 
     345    sanitized_allowed_protocols.each do |proto| 
     346      assert !contains_bad_protocols?('src', "#{proto}://good") 
     347    end 
     348  end 
     349 
     350  def test_should_reject_hex_codes_in_protocol 
     351    assert 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") 
     352    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>" 
     353  end 
     354 
     355  def test_should_block_script_tag 
     356    assert_sanitized %(<SCRIPT\nSRC=http://ha.ckers.org/xss.js></SCRIPT>), "" 
     357  end 
     358 
     359  [%(<IMG SRC="javascript:alert('XSS');">),  
     360   %(<IMG SRC=javascript:alert('XSS')>),  
     361   %(<IMG SRC=JaVaScRiPt:alert('XSS')>),  
     362   %(<IMG """><SCRIPT>alert("XSS")</SCRIPT>">), 
     363   %(<IMG SRC=javascript:alert(&quot;XSS&quot;)>), 
     364   %(<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>), 
     365   %(<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;>), 
     366   %(<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>), 
     367   %(<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>), 
     368   %(<IMG SRC="jav\tascript:alert('XSS');">), 
     369   %(<IMG SRC="jav&#x09;ascript:alert('XSS');">), 
     370   %(<IMG SRC="jav&#x0A;ascript:alert('XSS');">), 
     371   %(<IMG SRC="jav&#x0D;ascript:alert('XSS');">), 
     372   %(<IMG SRC=" &#14;  javascript:alert('XSS');">), 
     373   %(<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>)].each_with_index do |img_hack, i| 
     374    define_method "test_should_not_fall_for_xss_image_hack_#{i+1}" do 
     375      assert_sanitized img_hack, "<img>" 
     376    end 
     377  end 
     378   
     379  def test_should_sanitize_tag_broken_up_by_null 
     380    assert_sanitized %(<SCR\0IPT>alert(\"XSS\")</SCR\0IPT>), "alert(\"XSS\")" 
     381  end 
     382   
     383  def test_should_sanitize_invalid_script_tag 
     384    assert_sanitized %(<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>), "" 
     385  end 
     386   
     387  def test_should_sanitize_script_tag_with_multiple_open_brackets 
     388    assert_sanitized %(<<SCRIPT>alert("XSS");//<</SCRIPT>), "&lt;" 
     389    assert_sanitized %(<iframe src=http://ha.ckers.org/scriptlet.html\n<a), %(&lt;a) 
     390  end 
     391   
     392  def test_should_sanitize_unclosed_script 
     393    assert_sanitized %(<SCRIPT SRC=http://ha.ckers.org/xss.js?<B>), "<b>" 
     394  end 
     395   
     396  def test_should_sanitize_half_open_scripts 
     397    assert_sanitized %(<IMG SRC="javascript:alert('XSS')"), "<img>" 
     398  end 
     399   
     400  def test_should_not_fall_for_ridiculous_hack 
     401    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>) 
     402    assert_sanitized img_hack, "<img>" 
     403  end 
     404 
     405  def test_should_sanitize_attributes 
     406    assert_sanitized %(<SPAN title="'><script>alert()</script>">blah</SPAN>), %(<span title="'&gt;&lt;script&gt;alert()&lt;/script&gt;">blah</span>) 
     407  end 
     408 
     409  def test_should_sanitize_illegal_style_properties 
     410    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;) 
     411    expected = %(display: block; width: 100%; height: 100%; background-color: black; background-image: ; background-x: center; background-y: center;) 
     412    assert_equal expected, sanitize_css(raw) 
     413  end 
     414 
     415  def test_should_sanitize_xul_style_attributes 
     416    raw = %(-moz-binding:url('http://ha.ckers.org/xssmoz.xml#xss')) 
     417    assert_equal '', sanitize_css(raw) 
     418  end 
     419   
     420  def test_should_sanitize_invalid_tag_names 
     421    assert_sanitized(%(a b c<script/XSS src="http://ha.ckers.org/xss.js"></script>d e f), "a b cd e f") 
     422  end 
     423   
     424  def test_should_sanitize_non_alpha_and_non_digit_characters_in_tags 
     425    assert_sanitized('<a onclick!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>foo</a>', "<a>foo</a>") 
     426  end 
     427   
     428  def test_should_sanitize_invalid_tag_names_in_single_tags 
     429    assert_sanitized('<img/src="http://ha.ckers.org/xss.js"/>', "<img />") 
     430  end 
     431 
     432  def test_should_sanitize_img_dynsrc_lowsrc 
     433    assert_sanitized(%(<img lowsrc="javascript:alert('XSS')" />), "<img />") 
     434  end 
     435 
     436  def test_should_sanitize_div_background_image_unicode_encoded 
     437    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) 
     438    assert_equal '', sanitize_css(raw) 
     439  end 
     440 
     441  def test_should_sanitize_div_style_expression 
     442    raw = %(width: expression(alert('XSS'));) 
     443    assert_equal '', sanitize_css(raw) 
     444  end 
     445   
     446  def test_should_sanitize_style_attribute 
     447    raw = %(<div style="display:block; background:url(http://rubyonrails.com); background-image: url(rubyonrails)">foo</div>) 
     448    assert_equal %(<div style="display: block; background: ; background-image: ;">foo</div>), sanitize(raw, :attributes => 'style') 
     449  end 
     450 
     451  def test_should_sanitize_img_vbscript 
     452     assert_sanitized %(<img src='vbscript:msgbox("XSS")' />), '<img />' 
     453  end 
     454 
     455 
    293456  def test_cycle_class 
    294457    value = Cycle.new("one", 2, "3") 
     
    375538 
    376539  def test_strip_tags 
     540    assert_equal("Dont touch me", strip_tags("Dont touch me")) 
    377541    assert_equal("This is a test.", strip_tags("<p>This <u>is<u> a <a href='test.html'><strong>test</strong></a>.</p>")) 
     542    assert_equal("Weirdos", strip_tags("Wei<<a>a onclick='alert(document.cookie);'</a>/>rdos")) 
    378543    assert_equal("This is a test.", strip_tags("This is a test.")) 
    379544    assert_equal( 
     
    383548    [nil, '', '   '].each { |blank| assert_equal blank, strip_tags(blank) } 
    384549  end 
     550 
     551  def assert_sanitized(text, expected = nil) 
     552    assert_equal((expected || text), sanitize(text)) 
     553  end 
     554 
     555  # pull in configuration values from ActionView::Base 
     556  [:sanitized_protocol_separator, :sanitized_protocol_attributes, :sanitized_bad_tags, :sanitized_allowed_tags, :sanitized_allowed_attributes, :sanitized_allowed_protocols, :sanitized_allowed_css_properties, :sanitized_allowed_css_keywords, :sanitized_shorthand_css_properties, :sanitized_uri_attributes].each do |attr| 
     557    define_method attr do 
     558      ActionView::Base.send(attr) 
     559    end 
     560  end 
    385561end