Rails_xss Plugin

I installed the rails_xss plugin, for
the main purpose of seeing what will
break with Rails 3.0 (where the behaviour
of the plugin is the default). I think
I've fixed everything, but let me know if you
see stuff that is HTML-escaped, which
shouldn't be.

As a side benefit, we now use Erubis,
rather than ERB, to render templates.
They tell me it's faster ...
This commit is contained in:
Jacques Distler 2010-05-26 00:27:49 -05:00
parent d6be09e0f0
commit a5e08f7bcc
343 changed files with 43874 additions and 37 deletions

20
vendor/plugins/rails_xss/MIT-LICENSE vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2009 Koziarski Software Ltd.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,90 @@
RailsXss
========
This plugin replaces the default ERB template handlers with erubis, and switches the behaviour to escape by default rather than requiring you to escape. This is consistent with the behaviour in Rails 3.0.
Strings now have a notion of "html safe", which is false by default. Whenever rails copies a string into the response body it checks whether or not the string is safe, safe strings are copied verbatim into the response body, but unsafe strings are escaped first.
All the XSS-proof helpers like link_to and form_tag now return safe strings, and will continue to work unmodified. If you have your own helpers which return strings you *know* are safe, you will need to explicitly tell rails that they're safe. For an example, take the following helper.
def some_helper
(1..5).map do |i|
"<li>#{i}</li>"
end.join("\n")
end
With this plugin installed, the html will be escaped. So you will need to do one of the following:
1) Use the raw helper in your template. raw will ensure that your string is copied verbatim into the response body.
<%= raw some_helper %>
2) Mark the string as safe in the helper itself:
def some_helper
(1..5).map do |i|
"<li>#{i}</li>"
end.join("\n").html_safe
end
3) Use the safe_helper meta programming method:
module ApplicationHelper
def some_helper
#...
end
safe_helper :some_helper
end
Example
-------
BEFORE:
<%= params[:own_me] %> => XSS attack
<%=h params[:own_me] %> => No XSS
<%= @blog_post.content %> => Displays the HTML
AFTER:
<%= params[:own_me] %> => No XSS
<%=h params[:own_me] %> => No XSS (same result)
<%= @blog_post.content %> => *escapes* the HTML
<%= raw @blog_post.content %> => Displays the HTML
Gotchas
---
#### textilize and simple_format do *not* return safe strings
Both these methods support arbitrary HTML and are *not* safe to embed directly in your document. You'll need to do something like:
<%= sanitize(textilize(@blog_post.content_textile)) %>
#### Safe strings aren't magic.
Once a string has been marked as safe, the only operations which will maintain that HTML safety are String#<<, String#concat and String#+. All other operations are safety ignorant so it's still probably possible to break your app if you're doing something like
value = something_safe
value.gsub!(/a/, params[:own_me])
Don't do that.
#### String interpolation won't be safe, even when it 'should' be
value = "#{something_safe}#{something_else_safe}"
value.html_safe? # => false
This is intended functionality and can't be fixed.
Getting Started
===============
1. Install rails 2.3.8 or higher, or freeze rails from 2-3-stable.
2. Install erubis (gem install erubis)
3. Install this plugin (ruby script/plugin install git://github.com/rails/rails_xss.git)
4. Report anything that breaks.
Copyright (c) 2009 Koziarski Software Ltd, released under the MIT license. For full details see MIT-LICENSE included in this distribution.

23
vendor/plugins/rails_xss/Rakefile vendored Normal file
View file

@ -0,0 +1,23 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the rails_xss plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the rails_xss plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'RailsXss'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

7
vendor/plugins/rails_xss/init.rb vendored Normal file
View file

@ -0,0 +1,7 @@
unless $gems_rake_task
if Rails.version <= "2.3.7"
$stderr.puts "rails_xss requires Rails 2.3.8 or later. Please upgrade to enable automatic HTML safety."
else
require 'rails_xss'
end
end

View file

@ -0,0 +1,3 @@
require 'rails_xss/erubis'
require 'rails_xss/action_view'
require 'rails_xss/string_ext'

View file

@ -0,0 +1,83 @@
module ActionView
class Base
def self.xss_safe?
true
end
module WithSafeOutputBuffer
# Rails version of with_output_buffer uses '' as the default buf
def with_output_buffer(buf = ActiveSupport::SafeBuffer.new) #:nodoc:
super buf
end
end
include WithSafeOutputBuffer
end
module Helpers
module TextHelper
def concat(string, unused_binding = nil)
if unused_binding
ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller)
end
output_buffer.concat(string)
end
end
module TagHelper
private
def content_tag_string_with_escaping(name, content, options, escape = true)
content_tag_string_without_escaping(name, ERB::Util.h(content), options, escape)
end
alias_method_chain :content_tag_string, :escaping
end
end
end
module RailsXss
module SafeHelpers
def safe_helper(*names)
names.each do |helper_method_name|
aliased_target, punctuation = helper_method_name.to_s.sub(/([?!=])$/, ''), $1
module_eval <<-END
def #{aliased_target}_with_xss_safety#{punctuation}(*args, &block)
raw(#{aliased_target}_without_xss_safety#{punctuation}(*args, &block))
end
END
alias_method_chain helper_method_name, :xss_safety
end
end
end
module HelperOverrides
def link_to(*args, &block)
if block_given?
options = args.first || {}
html_options = args.second
concat(link_to(capture(&block), options, html_options))
else
name = args.first
options = args.second || {}
html_options = args.third
url = url_for(options)
if html_options
html_options = html_options.stringify_keys
href = html_options['href']
convert_options_to_javascript!(html_options, url)
tag_options = tag_options(html_options)
else
tag_options = nil
end
href_attr = "href=\"#{url}\"" unless href
"<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe
end
end
end
end
Module.class_eval { include RailsXss::SafeHelpers }
ActionController::Base.helper(RailsXss::HelperOverrides)

View file

@ -0,0 +1,33 @@
require 'erubis/helpers/rails_helper'
module RailsXss
class Erubis < ::Erubis::Eruby
def add_preamble(src)
src << "@output_buffer = ActiveSupport::SafeBuffer.new;"
end
def add_text(src, text)
return if text.empty?
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
def add_expr_literal(src, code)
if code =~ /\s*raw\s+(.*)/
src << "@output_buffer.safe_concat((" << $1 << ").to_s);"
else
src << '@output_buffer << ((' << code << ').to_s);'
end
end
def add_expr_escaped(src, code)
src << '@output_buffer << ' << escaped_expr(code) << ';'
end
def add_postamble(src)
src << '@output_buffer.to_s'
end
end
end
Erubis::Helpers::RailsHelper.engine_class = RailsXss::Erubis
Erubis::Helpers::RailsHelper.show_src = false

View file

@ -0,0 +1,52 @@
require 'active_support/deprecation'
ActiveSupport::SafeBuffer.class_eval do
def concat(value)
if value.html_safe?
super(value)
else
super(ERB::Util.h(value))
end
end
alias << concat
end
class String
def html_safe?
defined?(@_rails_html_safe)
end
def html_safe!
ActiveSupport::Deprecation.warn("Use html_safe with your strings instead of html_safe! See http://yehudakatz.com/2010/02/01/safebuffers-and-rails-3-0/ for the full story.", caller)
@_rails_html_safe = true
self
end
def add_with_safety(other)
result = add_without_safety(other)
if html_safe? && also_html_safe?(other)
result.html_safe!
else
result
end
end
alias_method :add_without_safety, :+
alias_method :+, :add_with_safety
def concat_with_safety(other_or_fixnum)
result = concat_without_safety(other_or_fixnum)
unless html_safe? && also_html_safe?(other_or_fixnum)
remove_instance_variable(:@_rails_html_safe) if defined?(@_rails_html_safe)
end
result
end
alias_method_chain :concat, :safety
undef_method :<<
alias_method :<<, :concat_with_safety
private
def also_html_safe?(other)
other.respond_to?(:html_safe?) && other.html_safe?
end
end

View file

@ -0,0 +1,4 @@
# desc "Explaining what the task does"
# task :rails_xss do
# # Task goes here
# end

View file

@ -0,0 +1,134 @@
require 'test_helper'
class RailsXssTest < ActiveSupport::TestCase
test "ERB::Util.h should mark its return value as safe and escape it" do
escaped = ERB::Util.h("<p>")
assert_equal "&lt;p&gt;", escaped
assert escaped.html_safe?
end
test "ERB::Util.h should leave previously safe strings alone " do
# TODO this seems easier to compose and reason about, but
# this should be verified
escaped = ERB::Util.h("<p>".html_safe)
assert_equal "<p>", escaped
assert escaped.html_safe?
end
test "ERB::Util.h should not implode when passed a non-string" do
assert_nothing_raised do
assert_equal "1", ERB::Util.h(1)
end
end
end
class DeprecatedOutputSafetyTest < ActiveSupport::TestCase
def setup
@string = "hello"
end
test "A string can be marked safe using html_safe!" do
assert_deprecated do
@string.html_safe!
assert @string.html_safe?
end
end
test "Marking a string safe returns the string using html_safe!" do
assert_deprecated do
assert_equal @string, @string.html_safe!
end
end
test "Adding a safe string to another safe string returns a safe string using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
@string.html_safe!
@combination = @other_string + @string
assert_equal "otherhello", @combination
assert @combination.html_safe?
end
end
test "Adding an unsafe string to a safe string returns an unsafe string using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
@combination = @other_string + "<foo>"
@other_combination = @string + "<foo>"
assert_equal "other<foo>", @combination
assert_equal "hello<foo>", @other_combination
assert !@combination.html_safe?
assert !@other_combination.html_safe?
end
end
test "Concatting safe onto unsafe yields unsafe using html_safe!" do
assert_deprecated do
@other_string = "other"
@string.html_safe!
@other_string.concat(@string)
assert !@other_string.html_safe?
end
end
test "Concatting unsafe onto safe yields unsafe using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
string = @other_string.concat("<foo>")
assert_equal "other<foo>", string
assert !string.html_safe?
end
end
test "Concatting safe onto safe yields safe using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
@string.html_safe!
@other_string.concat(@string)
assert @other_string.html_safe?
end
end
test "Concatting safe onto unsafe with << yields unsafe using html_safe!" do
assert_deprecated do
@other_string = "other"
@string.html_safe!
@other_string << @string
assert !@other_string.html_safe?
end
end
test "Concatting unsafe onto safe with << yields unsafe using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
string = @other_string << "<foo>"
assert_equal "other<foo>", string
assert !string.html_safe?
end
end
test "Concatting safe onto safe with << yields safe using html_safe!" do
assert_deprecated do
@other_string = "other".html_safe!
@string.html_safe!
@other_string << @string
assert @other_string.html_safe?
end
end
test "Concatting a fixnum to safe always yields safe using html_safe!" do
assert_deprecated do
@string.html_safe!
@string.concat(13)
assert_equal "hello".concat(13), @string
assert @string.html_safe?
end
end
end

View file

@ -0,0 +1,5 @@
abort 'RAILS_ROOT=/path/to/rails/2.3/app rake test' unless ENV['RAILS_ROOT']
require File.expand_path('config/environment', ENV['RAILS_ROOT'])
require File.expand_path('../../init', __FILE__)
require 'active_support/test_case'
require 'test/unit'