SafeIntern
Safe implemetation of String#intern and String#to_sym methods.
Description
One of the common security issues of Ruby applications is calling intern or
to_sym on strings from untrusted source. Since symbols are not garbage
collected in Ruby, yet take up some memory, this allows attacker to mount
Denial of Service attack. Just by feeding the application with large number of
strings and creating large number of symbols, memory consumption of the Ruby
process would grow till machine runs out of memory.
One approach to solving this problem is to prevent calls to .intern on untrusted strings by whitelisting expected inputs:
whitelist(untrusted_string).to_sym
This gets tedious very quickly - developer has to know all of the permitted values beforehand and has to maintain the list.
Another approach is to allow conversion from string to symbol, but prevent allocation of new (previously unseen) symbol. This works most of the time since all useful symbols are already allocated in application when its source is parsed.
How it works
Symbols are stored in a structure global_symbols which maintains mapping
between frozen strings that are the "name" of symbol and numeric IDs, which
are used by Ruby interpreter. All currently allocated Symbols can be listed
by calling Symbol.all_symbols.
Starting from Ruby 2.0, API provides rb_check_id function, which allows to
query global_symbols for particular symbol without allocating it. This is
much more efficient than doing something like
Symbol.all_symbols.map(&:to_s).include?(untrusted_string)
This gem is also compatible with Ruby 1.9.3, where it falls back to enumerating all Symbols and caching the results. It is slightly more efficient than Ruby implementation, but still much slower than the implementation for Ruby 2.0.
With the ability to query for known symbols, intern and to_sym methods can
be patched. There are two possible behaviours when called on unknown string:
- return nil
- raise exception
This gem provides implementation of both in SafeIntern::ExceptionPatch and
SafeIntern::NilPatch modules.
Usage
-
Install using gem command:
$ gem install safe_intern
or include in Gemfile:
gem 'safe_intern'
-
Patch the String:
require 'safe_intern/string'
This will patch String#intern and String#to_sym methods to throw
exception when new Symbol would be allocated. To return nil instead, patch
the String with
require 'safe_intern'
class ::String
prepend SafeIntern::NilPatch
end
Prepending the SafeIntern::NilPatch module is important, since including it
would result in the wrong order of method lookup.
Only particular strings can be patched too:
require 'safe_intern'
unstrusted_string.extend(SafeIntern::ExceptionPatch)
Calling .to_sym and .intern on unstrusted string that would result in
new symbol allocation returns nil or throws exception:
> untrusted_string.extend(SafeIntern::NilPatch)
=> "does_not_exist"
> untrusted_string.intern
=> nil
> untrusted_string.extend(SafeIntern::ExceptionPatch)
=> "does_not_exist"
> untrusted_string.intern
UnsafeInternException: UnsafeInternException
from /home/jrusnack/.gem/ruby/gems/safe_intern-1.0.0/lib/safe_intern/exception_patch.rb:7:in `intern'
from (irb):4
from /usr/bin/irb:12:in `<main>'
When String class is patched, creating new symbol from trusted string is possible with
trusted_string.intern(:allow_unsafe)
Requirements
- Ruby >= 1.9.3
See also
Similar functionality is provided by Symbol Lookup gem. Read about addition of new method to query for already allocated Symbols in Issue 7854.
Kudos to Martin Povolny for the initial idea.
License
SafeIntern is released under the MIT License.