Convert Strainer to white-list method protection

After moving the method existence check from Context into Strainer,
updated Strainer to only accept invokation methods that were added via
filter Modules, and done in a way that respond_to? is never called,
preventing unconstrained Symbol table growth.
This commit is contained in:
Jason Roelofs
2013-01-16 11:14:01 -05:00
parent a48e162237
commit 1300210f05
4 changed files with 70 additions and 42 deletions
+1 -5
View File
@@ -71,11 +71,7 @@ module Liquid
end
def invoke(method, *args)
if strainer.respond_to?(method)
strainer.__send__(method, *args)
else
args.first
end
strainer.invoke(method, *args)
end
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
+18 -20
View File
@@ -9,16 +9,11 @@ module Liquid
end
# Strainer is the parent class for the filters system.
# New filters are mixed into the strainer class which is then instanciated for each liquid template render run.
# New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
#
# One of the strainer's responsibilities is to keep malicious method calls out
# The Strainer only allows method calls defined in filters given to it via Strainer.global_filter,
# Context#add_filters or Template.register_filter
class Strainer < parent_object #:nodoc:
INTERNAL_METHOD = /^__/
@@required_methods = Set.new([:__id__, :__send__, :respond_to?, :kind_of?, :extend, :methods, :singleton_methods, :class, :object_id])
# Ruby 1.9.2 introduces Object#respond_to_missing?, which is invoked by Object#respond_to?
@@required_methods << :respond_to_missing? if Object.respond_to? :respond_to_missing?
@@filters = {}
def initialize(context)
@@ -36,19 +31,22 @@ module Liquid
strainer
end
def respond_to?(method, include_private = false)
method_name = method.to_s
return false if method_name =~ INTERNAL_METHOD
return false if @@required_methods.include?(method_name)
super
end
# remove all standard methods from the bucket so circumvent security
# problems
instance_methods.each do |m|
unless @@required_methods.include?(m.to_sym)
undef_method m
def invoke(method, *args)
if has_method?(method)
send(method, *args)
else
args.first
end
end
private
def has_method?(method)
methods_to_check = self.methods - self.class.public_instance_methods
methods_to_check.any? do |instance_method|
instance_method.to_s == method.to_s
end
end
end
end