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 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! |
Would this break queuing libraries like sidekiq? Seems like it would cause problems for a lot of legitimate use cases.
@HenleyChiu config/initializers
I'm getting this on 1.9.3p327:
undefined method `tagged_classes' for Psych:Module
I'm investigating, but any hint someone could provide?
@thbar: Just updated with a version that supports 1.9 and Psych.
@jroes: It will break some legitimate use cases, yes. This is only an option if you don't use one of those, but fortunately, many apps don't.
Personally, I think you're better off using Marshal
or similar if you really need to serialize arbitrary data, but existing code is potentially out of luck.
FWIW, I removed Syck references for Ruby 2.0 here:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Where do I put this?