Skip to content

Instantly share code, notes, and snippets.

@nelhage
Last active December 10, 2015 23:08
Show Gist options
  • Save nelhage/4507129 to your computer and use it in GitHub Desktop.
Save nelhage/4507129 to your computer and use it in GitHub Desktop.
Neuter YAML to help mitigate CVE-2013-0156-style attacks.
# 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!
@HenleyChiu
Copy link

Where do I put this?

@jroes
Copy link

jroes commented Jan 11, 2013

Would this break queuing libraries like sidekiq? Seems like it would cause problems for a lot of legitimate use cases.

@jdan
Copy link

jdan commented Jan 11, 2013

@HenleyChiu config/initializers

@thbar
Copy link

thbar commented Jan 11, 2013

I'm getting this on 1.9.3p327:

undefined method `tagged_classes' for Psych:Module

I'm investigating, but any hint someone could provide?

@nelhage
Copy link
Author

nelhage commented Jan 28, 2013

@thbar: Just updated with a version that supports 1.9 and Psych.

@nelhage
Copy link
Author

nelhage commented Jan 28, 2013

@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.

@thbar
Copy link

thbar commented Mar 25, 2014

FWIW, I removed Syck references for Ruby 2.0 here:

https://gist.github.com/thbar/9770758

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment