Last active
December 10, 2015 23:08
-
-
Save nelhage/4507129 to your computer and use it in GitHub Desktop.
Neuter YAML to help mitigate CVE-2013-0156-style attacks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# The fact that YAML.load will instantiate arbitrary ruby objects | |
# means that calling `YAML.load` on untrusted data is virtually always | |
# equivalent to executing arbitrary code in a complex app. | |
# This code fragment globally neuters YAML to disable this behavior, | |
# which should (hopefully) cut off all such attacks from the start. | |
# I don't promise this closes all possible attacks, but this closes | |
# off the trivial case. You should audit and upgrade all your | |
# dependencies, as well. | |
# Now with Psych/Ruby 1.9 support by @ebroder | |
def self.make_yaml_safe! | |
# Make syck-based YAML safe | |
require 'yaml' | |
require 'syck' | |
case RUBY_VERSION | |
when /^1\.8/ | |
syckmod = YAML | |
else | |
syckmod = Syck | |
end | |
# I've left in Symbol because it's common to rely on loading YAML config files | |
# with symbol keys. If you don't need them, remove it from this list -- | |
# loading attacker-provided symbols is an easy memory leak, and can potentially | |
# be used to tickle CVE-2012-5664-style bugs | |
whitelist_classes = [String, Hash, Symbol, Float, Array, | |
TrueClass, FalseClass, Integer, | |
Time, Date, NilClass] | |
syckmod.tagged_classes.delete_if { |k,v| !whitelist_classes.include?(v) } | |
syckmod.tagged_classes.freeze | |
# Make psych-based (1.9) YAML safe | |
if defined?(Psych) | |
Psych.const_set("UnsafeYAML", Class.new(StandardError)) | |
Psych.module_eval do | |
def self.load(yaml, *args) | |
result = parse(yaml, *args) | |
check_safety(result) | |
result ? result.to_ruby : result | |
end | |
private | |
def self.check_safety(o) | |
check_node(o) | |
case o | |
when Psych::Nodes::Scalar | |
when Psych::Nodes::Sequence | |
o.children.each {|child| check_safety(child)} | |
when Psych::Nodes::Mapping | |
o.children.each {|child| check_safety(child)} | |
when Psych::Nodes::Document | |
check_safety(o.root) | |
when Psych::Nodes::Stream | |
o.children.each {|child| check_safety(child)} | |
when Psych::Nodes::Alias | |
else | |
raise Psych::UnsafeYAML.new("Found unknown node type: #{o.class}") | |
end | |
end | |
def self.check_node(n) | |
unless n.tag.nil? || ['!ruby/sym', '!ruby/symbol'].include?(n.tag) | |
raise Psych::UnsafeYAML.new("Found node with tag: #{n.tag}") | |
end | |
end | |
end | |
# Force the default engine back to Psych, since we required Syck. | |
YAML::ENGINE.yamler = 'psych' | |
end | |
end | |
make_yaml_safe! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FWIW, I removed Syck references for Ruby 2.0 here:
https://gist.github.com/thbar/9770758