emk-safe_erb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +37 -0
- data/Rakefile +63 -0
- data/lib/safe_erb/action_view_extensions.rb +20 -0
- data/lib/safe_erb/common.rb +51 -0
- data/lib/safe_erb/erb_extensions.rb +52 -0
- data/lib/safe_erb/rails_1.rb +11 -0
- data/lib/safe_erb/rails_2.rb +11 -0
- data/lib/safe_erb/sqlite3_fix.rb +18 -0
- data/lib/safe_erb/tag_helper.rb +29 -0
- data/lib/safe_erb.rb +16 -0
- data/rails/init.rb +1 -0
- data/test/safe_erb_test.rb +20 -0
- data/test/tag_helper_test.rb +30 -0
- data/test/test_helper.rb +9 -0
- metadata +69 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2006 Shinya Kasatani
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= Safe ERB
|
2
|
+
|
3
|
+
== Overview
|
4
|
+
|
5
|
+
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.
|
6
|
+
|
7
|
+
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.
|
8
|
+
|
9
|
+
== About this version of Safe ERB
|
10
|
+
|
11
|
+
This fork of Safe ERB has been modified to work with Mephisto under Rails 2.2. It may work with other Rails applications. Patches are welcome!
|
12
|
+
|
13
|
+
== Installation
|
14
|
+
|
15
|
+
Just put this plugin into vendor/plugins directory in your Rails application. No configuration is needed.
|
16
|
+
|
17
|
+
This version of Safe ERB has been tested with Mephisto under Rails 2.2. It has been tested with SQLite3, and there's a decent chance it should work with PostgreSQL and MySQL (and any other database which properly taints the data returned from the database).
|
18
|
+
|
19
|
+
== Details
|
20
|
+
|
21
|
+
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.
|
22
|
+
|
23
|
+
The returned values from the following methods become untainted:
|
24
|
+
|
25
|
+
- ERB::Util#h
|
26
|
+
- ActionView::Helpers::TagHelper#escape
|
27
|
+
- ActionView::Helpers::TextHelper#strip_tags
|
28
|
+
|
29
|
+
Also, you can always untaint any string manually by calling "untaint" method (standard Ruby feature).
|
30
|
+
|
31
|
+
Several ActionView helpers have also been modified to untaint their return values. This is a temporary measure to get things working with Mephisto. See the source for details.
|
32
|
+
|
33
|
+
== Contact
|
34
|
+
|
35
|
+
The orignal safe_erb plugin was written by Shinya Kasatani <kasatani at gmail.com>.
|
36
|
+
|
37
|
+
This forked version was written by Eric Kidd, based on work by Matthew Bass.
|
data/Rakefile
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/specification'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
|
8
|
+
GEM = "safe_erb"
|
9
|
+
GEM_VERSION = "0.1.0"
|
10
|
+
SUMMARY = "Automatically detect improperty-escaped text in ERB templates"
|
11
|
+
AUTHORS = ["Shinya Kasatani", "Matthew Bass", "Eric Kidd"]
|
12
|
+
EMAIL = "git@randomhacks.net"
|
13
|
+
HOMEPAGE = "http://github.com/emk/safe_erb/tree/master"
|
14
|
+
|
15
|
+
spec = Gem::Specification.new do |s|
|
16
|
+
s.name = GEM
|
17
|
+
s.version = GEM_VERSION
|
18
|
+
s.platform = Gem::Platform::RUBY
|
19
|
+
s.summary = SUMMARY
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
s.files = FileList['{lib,rails,test}/**/*.rb', '[A-Z]*'].to_a
|
22
|
+
|
23
|
+
s.authors = AUTHORS
|
24
|
+
s.email = EMAIL
|
25
|
+
s.homepage = HOMEPAGE
|
26
|
+
|
27
|
+
s.rubyforge_project = GEM # GitHub bug, gem isn't being build when this miss
|
28
|
+
end
|
29
|
+
|
30
|
+
desc 'Default: run unit tests.'
|
31
|
+
task :default => :test
|
32
|
+
|
33
|
+
desc 'Test the safe_erb plugin.'
|
34
|
+
Rake::TestTask.new(:test) do |t|
|
35
|
+
t.libs << 'lib'
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
37
|
+
t.verbose = true
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Generate documentation for the safe_erb plugin.'
|
41
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = 'SafeERB'
|
44
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
45
|
+
rdoc.rdoc_files.include('README')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
49
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
50
|
+
pkg.gem_spec = spec
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Install the gem locally"
|
54
|
+
task :install => [:package] do
|
55
|
+
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "Create a gemspec file"
|
59
|
+
task :make_spec do
|
60
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
61
|
+
file.puts spec.to_ruby
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActionView
|
2
|
+
module TemplateHandlers
|
3
|
+
class ERB < TemplateHandler
|
4
|
+
def compile_with_safe_erb(template)
|
5
|
+
# This helps make new-style ActionMailer text templates do the
|
6
|
+
# right thing automatically. We will probably want to extend this
|
7
|
+
# to other kinds of templates eventually.
|
8
|
+
if template.filename.to_s =~ /\.text\.plain\.erb$/
|
9
|
+
::ERB.without_checking_tainted do
|
10
|
+
compile_without_safe_erb template
|
11
|
+
end
|
12
|
+
else
|
13
|
+
compile_without_safe_erb template
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method_chain :compile, :safe_erb
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# SafeERB
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'action_controller'
|
5
|
+
require 'action_view'
|
6
|
+
|
7
|
+
class ActionController::Base
|
8
|
+
# Object#taint is set when the request comes from FastCGI or WEBrick,
|
9
|
+
# but it is not set in Mongrel and also functional / integration testing
|
10
|
+
# so we'll set it anyways in the filter
|
11
|
+
before_filter :taint_request
|
12
|
+
|
13
|
+
def render_with_taint_logic(*args, &blk)
|
14
|
+
if @skip_checking_tainted
|
15
|
+
ERB.without_checking_tainted do
|
16
|
+
render_without_taint_logic(*args, &blk)
|
17
|
+
end
|
18
|
+
else
|
19
|
+
render_without_taint_logic(*args, &blk)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method_chain :render, :taint_logic
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def taint_hash(hash)
|
28
|
+
hash.each do |k, v|
|
29
|
+
case v
|
30
|
+
when String
|
31
|
+
v.taint
|
32
|
+
when Hash
|
33
|
+
taint_hash(v)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def taint_request
|
39
|
+
taint_hash(params)
|
40
|
+
cookies.each do |k, v|
|
41
|
+
v.taint
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class String
|
47
|
+
def concat_unless_tainted(str)
|
48
|
+
raise "attempted to output tainted string: #{str}" if str.is_a?(String) && str.tainted?
|
49
|
+
concat(str)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class ERB
|
2
|
+
# Should we check for tainted values when building ERB templates?
|
3
|
+
def self.check_tainted?
|
4
|
+
value = Thread.current[:safe_erb_check_tainted]
|
5
|
+
value.nil? ? true : value
|
6
|
+
end
|
7
|
+
|
8
|
+
# Turn ERB taint-checking on and off.
|
9
|
+
def self.check_tainted= value
|
10
|
+
Thread.current[:safe_erb_check_tainted] = value
|
11
|
+
end
|
12
|
+
|
13
|
+
# Skip taint checks within the specified block.
|
14
|
+
def self.without_checking_tainted #:yield:
|
15
|
+
saved_value = ERB.check_tainted?
|
16
|
+
ERB.check_tainted = false
|
17
|
+
begin
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
ERB.check_tainted = saved_value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :original_set_eoutvar, :set_eoutvar
|
25
|
+
|
26
|
+
def set_eoutvar(compiler, eoutvar = '_erbout')
|
27
|
+
original_set_eoutvar(compiler, eoutvar)
|
28
|
+
if ERB.check_tainted?
|
29
|
+
if compiler.respond_to?(:insert_cmd)
|
30
|
+
compiler.insert_cmd = "#{eoutvar}.concat_unless_tainted"
|
31
|
+
else
|
32
|
+
compiler.put_cmd = "#{eoutvar}.concat_unless_tainted"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module Util
|
38
|
+
alias_method :html_escape_without_untaint, :html_escape
|
39
|
+
|
40
|
+
def html_escape(s)
|
41
|
+
h = html_escape_without_untaint(s)
|
42
|
+
h.untaint
|
43
|
+
h
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :h, :html_escape
|
47
|
+
|
48
|
+
module_function :h
|
49
|
+
module_function :html_escape
|
50
|
+
module_function :html_escape_without_untaint
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module SQLite3
|
2
|
+
class ResultSet
|
3
|
+
# Add taint to data returned from SQLite3 database. This is based on a
|
4
|
+
# patch by Koji Shimada:
|
5
|
+
# http://rubyforge.org/tracker/index.php?func=detail&aid=20325&group_id=254&atid=1045
|
6
|
+
def next_with_tainting
|
7
|
+
row = next_without_tainting
|
8
|
+
case row
|
9
|
+
when Hash
|
10
|
+
row.each {|key, value| value.taint }
|
11
|
+
when Array
|
12
|
+
row.each {|column| column.taint }
|
13
|
+
end
|
14
|
+
row
|
15
|
+
end
|
16
|
+
alias_method_chain :next, :tainting
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
module TagHelper
|
4
|
+
def escape_once_with_untaint(html)
|
5
|
+
escape_once_without_untaint(html).untaint
|
6
|
+
end
|
7
|
+
|
8
|
+
alias_method_chain :escape_once, :untaint
|
9
|
+
end
|
10
|
+
|
11
|
+
module DateHelper
|
12
|
+
# TODO - This is almost certainly too aggressive, and it will need to
|
13
|
+
# be fine-tuned.
|
14
|
+
def datetime_select_with_untaint(*args)
|
15
|
+
datetime_select_without_untaint(*args).untaint
|
16
|
+
end
|
17
|
+
alias_method_chain :datetime_select, :untaint
|
18
|
+
end
|
19
|
+
|
20
|
+
module ActiveRecordHelper
|
21
|
+
# TODO - This is almost certainly too aggressive, and it will need to
|
22
|
+
# be fine-tuned.
|
23
|
+
def error_messages_for_with_untaint(*args)
|
24
|
+
error_messages_for_without_untaint(*args).untaint
|
25
|
+
end
|
26
|
+
alias_method_chain :error_messages_for, :untaint
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/safe_erb.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# SafeERB
|
2
|
+
|
3
|
+
require 'safe_erb/common'
|
4
|
+
require 'safe_erb/tag_helper'
|
5
|
+
require 'safe_erb/erb_extensions'
|
6
|
+
require 'safe_erb/action_view_extensions'
|
7
|
+
|
8
|
+
if Rails::VERSION::MAJOR >= 2
|
9
|
+
require 'safe_erb/rails_2'
|
10
|
+
else
|
11
|
+
require 'safe_erb/rails_1'
|
12
|
+
end
|
13
|
+
|
14
|
+
if ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::SQLite3Adapter)
|
15
|
+
require 'safe_erb/sqlite3_fix'
|
16
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_dependency 'safe_erb'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class SafeERBTest < Test::Unit::TestCase
|
4
|
+
def test_non_checking
|
5
|
+
ERB.without_checking_tainted do
|
6
|
+
src = ERB.new("<%= File.open('#{__FILE__}'){|f| f.read} %>", nil, '-').src
|
7
|
+
eval(src)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_checking
|
12
|
+
src = ERB.new("<%= File.open('#{__FILE__}'){|f| f.read} %>", nil, '-').src
|
13
|
+
assert_raise(RuntimeError) { eval(src) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_checking_non_tainted
|
17
|
+
src = ERB.new("<%= 'This string is not tainted' %>", nil, '-').src
|
18
|
+
eval(src)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
class TagHelperTest < Test::Unit::TestCase
|
4
|
+
include ActionView::Helpers::TagHelper
|
5
|
+
include ActionView::Helpers::DateHelper
|
6
|
+
include ActionView::Helpers::ActiveRecordHelper
|
7
|
+
|
8
|
+
def test_inclusion_in_taghelper
|
9
|
+
assert self.respond_to?(:escape_once_with_untaint)
|
10
|
+
assert self.respond_to?(:escape_once_without_untaint)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_taghelper_untaints
|
14
|
+
evil_str = "evil knievel".taint
|
15
|
+
assert !escape_once(evil_str).tainted?
|
16
|
+
assert escape_once_without_untaint(evil_str).tainted?
|
17
|
+
end
|
18
|
+
|
19
|
+
Post = Struct.new(:published_at)
|
20
|
+
def post
|
21
|
+
@post ||= Post.new(Time.now.taint)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_datetime_select_should_untaint
|
25
|
+
assert !datetime_select(:post, :published_at).tainted?
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO - Add tests for error_messages_for helper. This is a little
|
29
|
+
# tricky, because we'll need an ActiveRecord::Base instance.
|
30
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: emk-safe_erb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shinya Kasatani
|
8
|
+
- Matthew Bass
|
9
|
+
- Eric Kidd
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2008-12-16 00:00:00 -08:00
|
15
|
+
default_executable:
|
16
|
+
dependencies: []
|
17
|
+
|
18
|
+
description:
|
19
|
+
email: git@randomhacks.net
|
20
|
+
executables: []
|
21
|
+
|
22
|
+
extensions: []
|
23
|
+
|
24
|
+
extra_rdoc_files: []
|
25
|
+
|
26
|
+
files:
|
27
|
+
- lib/safe_erb/action_view_extensions.rb
|
28
|
+
- lib/safe_erb/common.rb
|
29
|
+
- lib/safe_erb/erb_extensions.rb
|
30
|
+
- lib/safe_erb/rails_1.rb
|
31
|
+
- lib/safe_erb/rails_2.rb
|
32
|
+
- lib/safe_erb/sqlite3_fix.rb
|
33
|
+
- lib/safe_erb/tag_helper.rb
|
34
|
+
- lib/safe_erb.rb
|
35
|
+
- rails/init.rb
|
36
|
+
- test/safe_erb_test.rb
|
37
|
+
- test/tag_helper_test.rb
|
38
|
+
- test/test_helper.rb
|
39
|
+
- MIT-LICENSE
|
40
|
+
- Rakefile
|
41
|
+
- README
|
42
|
+
has_rdoc: false
|
43
|
+
homepage: http://github.com/emk/safe_erb/tree/master
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project: safe_erb
|
64
|
+
rubygems_version: 1.2.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: Automatically detect improperty-escaped text in ERB templates
|
68
|
+
test_files: []
|
69
|
+
|