Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial import

git-svn-id: svn://rubyforge.org/var/svn/safe-erb/plugins/safe_erb@1 ae60d643-a0c1-4d14-af8c-9187da1253ed
  • Loading branch information...
commit f0828e075a555f80b1fc8c13db45bead1f5e8318 0 parents
shinya authored
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2006 Shinya Kasatani
+
+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.
33 README
@@ -0,0 +1,33 @@
+= Safe ERB
+
+== Overview
+
+Safe ERB lets you make sure that the string written by "<%= %>" in your rhtml template is escaped correctly. If you try to show the attributes in the ActiveRecord instance read from the database or the parameters received from the request without escaping them using "h" method, an exception will be raised. This will significantly reduce the possibility of putting cross-site scripting vulnerability into your web application.
+
+The check is done using "tainted?" method in Object class which is a standard feature provided by Ruby - the string is "tainted" when it is read from IO. When ERB::Util#h method is called, this plugin "untaints" the string, and when "<%= %>" is called in your rhtml template, it raises an exception if the string you are trying to show is tainted.
+
+== Installation
+
+Just put this plugin into vendor/plugins directory in your Rails application. No configuration is needed.
+
+== Details
+
+The string becomes tainted when it is read from IO, such as the data read from the DB or HTTP request. However, the request parameters are not tainted in functional and integration tests, and also if your server is Mongrel. Hence this plugin installs before_filter into ActionController::Base that always taints request parameters and cookies.
+
+The taint check is done when the ERB template is complied from following methods in ActionController::Base:
+
+- render_template
+- render_file
+
+The check is limited to these methods so that it won't affect other parts of Rails using ERB, such as generators and the views for ActionMailer. To skip checking for specific controllers or actions, you can set @skip_checking_tainted variable to true in your filter or action.
+
+The returned values from the following methods become untainted:
+
+- ERB::Util#h
+- ActionView::Helpers::TextHelper#strip_tags
+
+Also, you can always untaint any string manually by calling "untaint" method (standard Ruby feature).
+
+== Contact
+
+Shinya Kasatani <kasatani at gmail.com>
22 Rakefile
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the safe_erb plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the safe_erb plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'SafeERB'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
3  init.rb
@@ -0,0 +1,3 @@
+# Include hook code here
+
+require 'safe_erb'
1  install.rb
@@ -0,0 +1 @@
+# Install hook code here
117 lib/safe_erb.rb
@@ -0,0 +1,117 @@
+# SafeERB
+
+require 'erb'
+require 'action_controller'
+require 'action_view'
+
+class ActionController::Base
+ # Object#taint is set when the request comes from FastCGI or WEBrick,
+ # but it is not set in Mongrel and also functional / integration testing
+ # so we'll set it anyways in the filter
+ before_filter :taint_request
+
+ private
+
+ def taint_hash(hash)
+ hash.each do |k, v|
+ case v
+ when String
+ v.taint
+ when Hash
+ taint_hash(v)
+ end
+ end
+ end
+
+ def taint_request
+ taint_hash(params)
+ cookies.each do |k, v|
+ v.taint
+ end
+ end
+
+ public
+
+ alias_method :render_template_without_checking_tainted, :render_template
+
+ def render_template(template, status = nil, type = :rhtml, local_assigns = {})
+ if @skip_checking_tainted
+ render_template_without_checking_tainted(template, status, type, local_assigns)
+ else
+ ERB.with_checking_tainted do
+ render_template_without_checking_tainted(template, status, type, local_assigns)
+ end
+ end
+ end
+
+ alias_method :render_file_without_checking_tainted, :render_file
+
+ def render_file(template_path, status = nil, use_full_path = false, locals = {})
+ if @skip_checking_tainted
+ render_file_without_checking_tainted(template_path, status, use_full_path, locals)
+ else
+ ERB.with_checking_tainted do
+ render_file_without_checking_tainted(template_path, status, use_full_path, locals)
+ end
+ end
+ end
+end
+
+class String
+ def concat_unless_tainted(str)
+ raise "attempted to output tainted string: #{str}" if str.is_a?(String) && str.tainted?
+ concat(str)
+ end
+end
+
+class ERB
+ cattr_accessor :check_tainted
+ alias_method :original_set_eoutvar, :set_eoutvar
+
+ def self.with_checking_tainted(&block)
+ # not thread safe
+ ERB.check_tainted = true
+ begin
+ yield
+ ensure
+ ERB.check_tainted = false
+ end
+ end
+
+ def set_eoutvar(compiler, eoutvar = '_erbout')
+ original_set_eoutvar(compiler, eoutvar)
+ if check_tainted
+ if compiler.respond_to?(:insert_cmd)
+ compiler.insert_cmd = "#{eoutvar}.concat_unless_tainted"
+ else
+ compiler.put_cmd = "#{eoutvar}.concat_unless_tainted"
+ end
+ end
+ end
+
+ module Util
+ alias_method :html_escape_without_untaint, :html_escape
+
+ def html_escape(s)
+ h = html_escape_without_untaint(s)
+ h.untaint
+ h
+ end
+
+ alias_method :h, :html_escape
+
+ module_function :h
+ module_function :html_escape
+ module_function :html_escape_without_untaint
+ end
+end
+
+module ActionView::Helpers::TextHelper
+ alias_method :strip_tags_without_untaint, :strip_tags
+
+ def strip_tags(html)
+ str = strip_tags_without_untaint(html)
+ str.untaint
+ str
+ end
+end
4 tasks/safe_erb_tasks.rake
@@ -0,0 +1,4 @@
+# desc "Explaining what the task does"
+# task :safe_erb do
+# # Task goes here
+# end
23 test/safe_erb_test.rb
@@ -0,0 +1,23 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../../../config/environment')
+require 'test_help'
+
+class SafeERBTest < Test::Unit::TestCase
+ def test_non_checking
+ src = ERB.new("<%= File.open('#{__FILE__}'){|f| f.read} %>", nil, '-').src
+ eval(src)
+ end
+
+ def test_checking
+ ERB.with_checking_tainted do
+ src = ERB.new("<%= File.open('#{__FILE__}'){|f| f.read} %>", nil, '-').src
+ assert_raise(RuntimeError) { eval(src) }
+ end
+ end
+
+ def test_checking_non_tainted
+ ERB.with_checking_tainted do
+ src = ERB.new("<%= 'This string is not tainted' %>", nil, '-').src
+ eval(src)
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.