safe_yaml 0.5.2 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +25 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -5
- data/lib/safe_yaml/psych_handler.rb +5 -3
- data/lib/safe_yaml/syck_resolver.rb +14 -7
- data/lib/safe_yaml/transform/to_date.rb +1 -1
- data/lib/safe_yaml/transform/to_float.rb +1 -1
- data/lib/safe_yaml/transform/to_integer.rb +1 -1
- data/lib/safe_yaml/transform/to_symbol.rb +2 -2
- data/lib/safe_yaml/transform/to_time.rb +1 -1
- data/lib/safe_yaml/transform.rb +3 -5
- data/lib/safe_yaml/version.rb +1 -1
- data/lib/safe_yaml.rb +87 -12
- data/run_specs_all_ruby_versions.sh +3 -0
- data/safe_yaml.gemspec +2 -2
- data/spec/safe_yaml_spec.rb +142 -18
- data/spec/shared_specs.rb +18 -8
- data/spec/transform/to_date_spec.rb +19 -0
- data/spec/transform/to_float_spec.rb +15 -0
- data/spec/transform/to_integer_spec.rb +15 -0
- data/spec/transform/to_symbol_spec.rb +45 -0
- data/spec/transform/to_time_spec.rb +18 -0
- metadata +19 -5
data/.travis.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
language: ruby
|
2
|
+
before_install:
|
3
|
+
- gem install bundler
|
4
|
+
script: "bundle exec rake spec"
|
5
|
+
rvm:
|
6
|
+
- ruby-head
|
7
|
+
- 2.0.0
|
8
|
+
- 1.9.3
|
9
|
+
- 1.9.2
|
10
|
+
- 1.8.7
|
11
|
+
- rbx-19mode
|
12
|
+
- rbx-18mode
|
13
|
+
- jruby-head
|
14
|
+
- jruby-19mode
|
15
|
+
- jruby-18mode
|
16
|
+
- ree
|
17
|
+
matrix:
|
18
|
+
allow_failures:
|
19
|
+
- rvm: ruby-head
|
20
|
+
- rvm: rbx-19mode
|
21
|
+
- rvm: rbx-18mode
|
22
|
+
- rvm: jruby-head
|
23
|
+
- rvm: jruby-19mode
|
24
|
+
- rvm: jruby-18mode
|
25
|
+
- rvm: ree
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Dan Tao
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
SafeYAML
|
2
2
|
========
|
3
3
|
|
4
|
-
The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the one recently discovered in Rails](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/)).
|
4
|
+
The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the one recently discovered in Rails](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/) (or [this one](http://www.h-online.com/open/news/item/Rails-developers-close-another-extremely-critical-flaw-1793511.html))).
|
5
5
|
|
6
6
|
Installation
|
7
7
|
------------
|
@@ -18,8 +18,8 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
$ gem install safe_yaml
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
Purpose
|
22
|
+
-------
|
23
23
|
|
24
24
|
Suppose your application were to contain some code like this:
|
25
25
|
|
@@ -62,6 +62,25 @@ With SafeYAML, that attacker would be thwarted:
|
|
62
62
|
> YAML.load(yaml)
|
63
63
|
=> {"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}
|
64
64
|
|
65
|
+
Usage
|
66
|
+
-----
|
67
|
+
|
68
|
+
`YAML.safe_load` will load YAML without allowing arbitrary object deserialization.
|
69
|
+
|
70
|
+
`YAML.unsafe_load` will exhibit Ruby's built-in behavior: to allow the deserialization of arbitrary objects.
|
71
|
+
|
72
|
+
By default, when you require the safe_yaml gem in your project, `YAML.load` is patched to internally call `safe_load`. The patched method also accepts a `:safe` flag to specify which version to use:
|
73
|
+
|
74
|
+
# Ruby >= 1.9.3
|
75
|
+
YAML.load(yaml, filename, :safe => true) # calls safe_load
|
76
|
+
YAML.load(yaml, filename, :safe => false) # calls unsafe_load
|
77
|
+
|
78
|
+
# Ruby < 1.9.3
|
79
|
+
YAML.load(yaml, :safe => true) # calls safe_load
|
80
|
+
YAML.load(yaml, :safe => false) # calls unsafe_load
|
81
|
+
|
82
|
+
The default behavior can be switched to unsafe loading by calling `YAML.enable_arbitrary_object_deserialization!`. In this case, the `:safe` flag still has the same effect, but the defaults are reversed (so calling `YAML.load` will have the same behavior as if the safe_yaml gem weren't required).
|
83
|
+
|
65
84
|
Notes
|
66
85
|
-----
|
67
86
|
|
@@ -76,11 +95,19 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
|
|
76
95
|
- Booleans
|
77
96
|
- Nils
|
78
97
|
|
79
|
-
Additionally, symbols
|
98
|
+
Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!`.
|
80
99
|
|
81
|
-
|
100
|
+
Caveat
|
101
|
+
------
|
102
|
+
|
103
|
+
Obviously this gem is quite young, and so the API may (read: will) change in future versions. The goal of the gem is to make it as easy as possible to protect existing applications from object deserialization exploits. Any and all feedback is more than welcome.
|
82
104
|
|
83
105
|
Requirements
|
84
106
|
------------
|
85
107
|
|
86
108
|
SafeYAML requires Ruby 1.8.7 or newer and works with both [Syck](http://www.ruby-doc.org/stdlib-1.8.7/libdoc/yaml/rdoc/YAML.html) and [Psych](http://github.com/tenderlove/psych).
|
109
|
+
|
110
|
+
Code Status
|
111
|
+
-------------
|
112
|
+
|
113
|
+
[![Build Status](https://secure.travis-ci.org/dtao/safe_yaml.png)](http://travis-ci.org/dtao/safe_yaml)
|
@@ -5,14 +5,16 @@ module SafeYAML
|
|
5
5
|
def initialize
|
6
6
|
@anchors = {}
|
7
7
|
@stack = []
|
8
|
+
@current_key = nil
|
9
|
+
@result = nil
|
8
10
|
end
|
9
11
|
|
10
12
|
def result
|
11
13
|
@result
|
12
14
|
end
|
13
15
|
|
14
|
-
def add_to_current_structure(value, anchor=nil)
|
15
|
-
value = Transform.to_proper_type(value)
|
16
|
+
def add_to_current_structure(value, anchor=nil, quoted=nil)
|
17
|
+
value = Transform.to_proper_type(value, quoted)
|
16
18
|
|
17
19
|
@anchors[anchor] = value if anchor
|
18
20
|
|
@@ -60,7 +62,7 @@ module SafeYAML
|
|
60
62
|
end
|
61
63
|
|
62
64
|
def scalar(value, anchor, tag, plain, quoted, style)
|
63
|
-
add_to_current_structure(value, anchor)
|
65
|
+
add_to_current_structure(value, anchor, quoted)
|
64
66
|
end
|
65
67
|
|
66
68
|
def start_mapping(anchor, tag, implicit, style)
|
@@ -1,19 +1,23 @@
|
|
1
1
|
module SafeYAML
|
2
2
|
class SyckResolver
|
3
|
+
QUOTE_STYLES = [:quote1, :quote2]
|
4
|
+
|
3
5
|
def resolve_node(node)
|
4
6
|
case node.kind
|
5
7
|
when :map
|
6
|
-
return resolve_map(node
|
8
|
+
return resolve_map(node)
|
7
9
|
when :seq
|
8
|
-
return resolve_seq(node
|
10
|
+
return resolve_seq(node)
|
9
11
|
when :scalar
|
10
|
-
return resolve_scalar(node
|
12
|
+
return resolve_scalar(node)
|
11
13
|
else
|
12
14
|
raise "Don't know how to resolve a '#{node.kind}' node!"
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
def resolve_map(
|
18
|
+
def resolve_map(node)
|
19
|
+
map = node.value
|
20
|
+
|
17
21
|
hash = {}
|
18
22
|
map.each do |key_node, value_node|
|
19
23
|
if resolve_node(key_node) == "<<"
|
@@ -22,15 +26,18 @@ module SafeYAML
|
|
22
26
|
hash[resolve_node(key_node)] = resolve_node(value_node)
|
23
27
|
end
|
24
28
|
end
|
29
|
+
|
25
30
|
return hash
|
26
31
|
end
|
27
32
|
|
28
|
-
def resolve_seq(
|
33
|
+
def resolve_seq(node)
|
34
|
+
seq = node.value
|
35
|
+
|
29
36
|
seq.map { |node| resolve_node(node) }
|
30
37
|
end
|
31
38
|
|
32
|
-
def resolve_scalar(
|
33
|
-
Transform.to_proper_type(
|
39
|
+
def resolve_scalar(node)
|
40
|
+
Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)))
|
34
41
|
end
|
35
42
|
end
|
36
43
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module SafeYAML
|
2
2
|
class Transform
|
3
3
|
class ToSymbol
|
4
|
-
MATCHER =
|
4
|
+
MATCHER = /\A:\w+\Z/.freeze
|
5
5
|
|
6
6
|
def transform?(value)
|
7
|
-
return false
|
7
|
+
return false unless SafeYAML::OPTIONS[:enable_symbol_parsing] && MATCHER.match(value)
|
8
8
|
return true, value[1..-1].to_sym
|
9
9
|
end
|
10
10
|
end
|
@@ -3,7 +3,7 @@ module SafeYAML
|
|
3
3
|
class ToTime
|
4
4
|
# There isn't a missing '$' there; YAML itself seems to ignore everything at the end of a
|
5
5
|
# string that otherwise resembles a time.
|
6
|
-
MATCHER =
|
6
|
+
MATCHER = /\A\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,5})?/.freeze
|
7
7
|
|
8
8
|
def transform?(value)
|
9
9
|
return false unless MATCHER.match(value)
|
data/lib/safe_yaml/transform.rb
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
module SafeYAML
|
2
2
|
class Transform
|
3
|
-
OPTIONS = {
|
4
|
-
:enable_symbol_parsing => false
|
5
|
-
}
|
6
|
-
|
7
3
|
TRANSFORMERS = [
|
8
4
|
Transform::ToSymbol.new,
|
9
5
|
Transform::ToInteger.new,
|
@@ -14,7 +10,9 @@ module SafeYAML
|
|
14
10
|
Transform::ToTime.new
|
15
11
|
]
|
16
12
|
|
17
|
-
def self.to_proper_type(value)
|
13
|
+
def self.to_proper_type(value, quoted=false)
|
14
|
+
return value if quoted
|
15
|
+
|
18
16
|
if value.is_a?(String)
|
19
17
|
TRANSFORMERS.each do |transformer|
|
20
18
|
success, transformed_value = transformer.transform?(value)
|
data/lib/safe_yaml/version.rb
CHANGED
data/lib/safe_yaml.rb
CHANGED
@@ -9,7 +9,34 @@ require "safe_yaml/transform/to_time"
|
|
9
9
|
require "safe_yaml/transform"
|
10
10
|
require "safe_yaml/version"
|
11
11
|
|
12
|
+
module SafeYAML
|
13
|
+
OPTIONS = {
|
14
|
+
:enable_symbol_parsing => false,
|
15
|
+
:enable_arbitrary_object_deserialization => false
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
12
19
|
module YAML
|
20
|
+
def self.load_with_options(yaml, options={})
|
21
|
+
safe_mode = safe_mode_from_options("load", options)
|
22
|
+
|
23
|
+
arguments = [yaml]
|
24
|
+
if RUBY_VERSION >= "1.9.3"
|
25
|
+
arguments << options[:filename]
|
26
|
+
end
|
27
|
+
|
28
|
+
safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.load_file_with_options(file, options={})
|
32
|
+
safe_mode = safe_mode_from_options("load_file", options)
|
33
|
+
safe_mode ? safe_load_file(file) : unsafe_load_file(file)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.load_with_filename_and_options(yaml, filename=nil, options={})
|
37
|
+
load_with_options(yaml, options.merge(:filename => filename))
|
38
|
+
end
|
39
|
+
|
13
40
|
if RUBY_VERSION >= "1.9.3"
|
14
41
|
require "safe_yaml/psych_handler"
|
15
42
|
def self.safe_load(yaml, filename=nil)
|
@@ -18,9 +45,13 @@ module YAML
|
|
18
45
|
return safe_handler.result
|
19
46
|
end
|
20
47
|
|
21
|
-
def self.
|
48
|
+
def self.safe_load_file(filename)
|
49
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f, filename }
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.unsafe_load_file(filename)
|
22
53
|
# https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
|
23
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.
|
54
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
|
24
55
|
end
|
25
56
|
|
26
57
|
elsif RUBY_VERSION == "1.9.2"
|
@@ -31,9 +62,13 @@ module YAML
|
|
31
62
|
return safe_handler.result
|
32
63
|
end
|
33
64
|
|
34
|
-
def self.
|
65
|
+
def self.safe_load_file(filename)
|
66
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f }
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.unsafe_load_file(filename)
|
35
70
|
# https://github.com/tenderlove/psych/blob/v1.2.0/lib/psych.rb#L228-230
|
36
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.
|
71
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f }
|
37
72
|
end
|
38
73
|
|
39
74
|
else
|
@@ -44,22 +79,62 @@ module YAML
|
|
44
79
|
return safe_resolver.resolve_node(tree)
|
45
80
|
end
|
46
81
|
|
47
|
-
def self.
|
82
|
+
def self.safe_load_file(filename)
|
83
|
+
File.open(filename) { |f| self.safe_load f }
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.unsafe_load_file(filename)
|
48
87
|
# https://github.com/indeyets/syck/blob/master/ext/ruby/lib/yaml.rb#L133-135
|
49
|
-
File.open(filename) { |f| self.
|
88
|
+
File.open(filename) { |f| self.unsafe_load f }
|
50
89
|
end
|
51
90
|
end
|
52
91
|
|
53
92
|
class << self
|
54
|
-
alias_method :
|
55
|
-
alias_method :load, :safe_load
|
93
|
+
alias_method :unsafe_load, :load
|
56
94
|
|
57
|
-
|
58
|
-
|
95
|
+
if RUBY_VERSION >= "1.9.3"
|
96
|
+
alias_method :load, :load_with_filename_and_options
|
97
|
+
else
|
98
|
+
alias_method :load, :load_with_options
|
59
99
|
end
|
60
100
|
|
61
|
-
|
62
|
-
|
101
|
+
alias_method :load_file, :load_file_with_options
|
102
|
+
|
103
|
+
def enable_symbol_parsing?
|
104
|
+
SafeYAML::OPTIONS[:enable_symbol_parsing]
|
105
|
+
end
|
106
|
+
|
107
|
+
def enable_symbol_parsing!
|
108
|
+
SafeYAML::OPTIONS[:enable_symbol_parsing] = true
|
109
|
+
end
|
110
|
+
|
111
|
+
def disable_symbol_parsing!
|
112
|
+
SafeYAML::OPTIONS[:enable_symbol_parsing] = false
|
113
|
+
end
|
114
|
+
|
115
|
+
def enable_arbitrary_object_deserialization?
|
116
|
+
SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
|
117
|
+
end
|
118
|
+
|
119
|
+
def enable_arbitrary_object_deserialization!
|
120
|
+
SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = true
|
121
|
+
end
|
122
|
+
|
123
|
+
def disable_arbitrary_object_deserialization!
|
124
|
+
SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = false
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
def self.safe_mode_from_options(method, options={})
|
130
|
+
safe_mode = options[:safe]
|
131
|
+
|
132
|
+
if safe_mode.nil?
|
133
|
+
mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
|
134
|
+
Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode."
|
135
|
+
safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
|
63
136
|
end
|
137
|
+
|
138
|
+
safe_mode
|
64
139
|
end
|
65
140
|
end
|
data/safe_yaml.gemspec
CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |gem|
|
|
6
6
|
gem.version = SafeYAML::VERSION
|
7
7
|
gem.authors = "Dan Tao"
|
8
8
|
gem.email = "daniel.tao@gmail.com"
|
9
|
-
gem.description = %q{Parse YAML safely, without that pesky arbitrary
|
9
|
+
gem.description = %q{Parse YAML safely, without that pesky arbitrary object deserialization vulnerability}
|
10
10
|
gem.summary = %q{SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.}
|
11
11
|
gem.homepage = "http://dtao.github.com/safe_yaml/"
|
12
|
-
|
12
|
+
gem.license = "MIT"
|
13
13
|
gem.files = `git ls-files`.split($\)
|
14
14
|
gem.test_files = gem.files.grep(%r{^spec/})
|
15
15
|
gem.require_paths = ["lib"]
|
data/spec/safe_yaml_spec.rb
CHANGED
@@ -3,42 +3,50 @@ require File.join(File.dirname(__FILE__), "spec_helper")
|
|
3
3
|
require "exploitable_back_door"
|
4
4
|
|
5
5
|
describe YAML do
|
6
|
+
# Essentially stolen from:
|
7
|
+
# https://github.com/rails/rails/blob/3-2-stable/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L10-25
|
8
|
+
def silence_warnings
|
9
|
+
$VERBOSE = nil; yield
|
10
|
+
ensure
|
11
|
+
$VERBOSE = true
|
12
|
+
end
|
13
|
+
|
6
14
|
before :each do
|
7
|
-
YAML.
|
15
|
+
YAML.disable_symbol_parsing!
|
8
16
|
end
|
9
17
|
|
10
|
-
describe "
|
18
|
+
describe "unsafe_load" do
|
11
19
|
if RUBY_VERSION >= "1.9.3"
|
12
20
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
13
|
-
backdoor = YAML.
|
21
|
+
backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
14
22
|
backdoor.should be_exploited_through_setter
|
15
23
|
end
|
16
24
|
|
17
25
|
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
18
|
-
backdoor = YAML.
|
26
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
19
27
|
backdoor.should be_exploited_through_init_with
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
31
|
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
24
|
-
backdoor = YAML.
|
32
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
25
33
|
backdoor.should be_exploited_through_ivars
|
26
34
|
end
|
27
35
|
end
|
28
36
|
|
29
|
-
describe "
|
37
|
+
describe "safe_load" do
|
30
38
|
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
31
|
-
object = YAML.
|
39
|
+
object = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
32
40
|
object.should_not be_a(ExploitableBackDoor)
|
33
41
|
end
|
34
42
|
|
35
43
|
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
36
|
-
object = YAML.
|
44
|
+
object = YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
37
45
|
object.should_not be_a(ExploitableBackDoor)
|
38
46
|
end
|
39
47
|
|
40
48
|
it "loads a plain ol' YAML document just fine" do
|
41
|
-
result = YAML.
|
49
|
+
result = YAML.safe_load <<-YAML.unindent
|
42
50
|
foo:
|
43
51
|
number: 1
|
44
52
|
string: Hello, there!
|
@@ -59,7 +67,7 @@ describe YAML do
|
|
59
67
|
end
|
60
68
|
|
61
69
|
it "works for YAML documents with anchors and aliases" do
|
62
|
-
result = YAML.
|
70
|
+
result = YAML.safe_load <<-YAML
|
63
71
|
- &id001 {}
|
64
72
|
- *id001
|
65
73
|
- *id001
|
@@ -69,7 +77,7 @@ describe YAML do
|
|
69
77
|
end
|
70
78
|
|
71
79
|
it "works for YAML documents with sections" do
|
72
|
-
result = YAML.
|
80
|
+
result = YAML.safe_load <<-YAML
|
73
81
|
mysql: &mysql
|
74
82
|
adapter: mysql
|
75
83
|
pool: 30
|
@@ -102,34 +110,150 @@ describe YAML do
|
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
105
|
-
describe "
|
113
|
+
describe "unsafe_load_file" do
|
106
114
|
if RUBY_VERSION >= "1.9.3"
|
107
115
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
108
|
-
backdoor = YAML.
|
116
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
|
109
117
|
backdoor.should be_exploited_through_setter
|
110
118
|
end
|
111
119
|
|
112
120
|
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
113
|
-
backdoor = YAML.
|
121
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
114
122
|
backdoor.should be_exploited_through_init_with
|
115
123
|
end
|
116
124
|
end
|
117
125
|
|
118
126
|
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
119
|
-
backdoor = YAML.
|
127
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
120
128
|
backdoor.should be_exploited_through_ivars
|
121
129
|
end
|
122
130
|
end
|
123
131
|
|
124
|
-
describe "
|
132
|
+
describe "safe_load_file" do
|
125
133
|
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
126
|
-
object = YAML.
|
134
|
+
object = YAML.safe_load_file "spec/exploit.1.9.3.yaml"
|
127
135
|
object.should_not be_a(ExploitableBackDoor)
|
128
136
|
end
|
129
137
|
|
130
138
|
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
131
|
-
object = YAML.
|
139
|
+
object = YAML.safe_load_file "spec/exploit.1.9.2.yaml"
|
132
140
|
object.should_not be_a(ExploitableBackDoor)
|
133
141
|
end
|
134
142
|
end
|
143
|
+
|
144
|
+
describe "load" do
|
145
|
+
let (:arguments) {
|
146
|
+
if RUBY_VERSION >= "1.9.3"
|
147
|
+
["foo: bar", nil]
|
148
|
+
else
|
149
|
+
["foo: bar"]
|
150
|
+
end
|
151
|
+
}
|
152
|
+
|
153
|
+
it "issues a warning if the :safe option is omitted" do
|
154
|
+
silence_warnings do
|
155
|
+
Kernel.should_receive(:warn)
|
156
|
+
YAML.load(*arguments)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it "doesn't issue a warning as long as the :safe option is specified" do
|
161
|
+
Kernel.should_not_receive(:warn)
|
162
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
163
|
+
end
|
164
|
+
|
165
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
166
|
+
silence_warnings do
|
167
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
168
|
+
YAML.load(*arguments)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it "calls #safe_load if the :safe option is set to true" do
|
173
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
174
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
175
|
+
end
|
176
|
+
|
177
|
+
it "calls #unsafe_load if the :safe option is set to false" do
|
178
|
+
YAML.should_receive(:unsafe_load).with(*arguments)
|
179
|
+
YAML.load(*(arguments + [{:safe => false}]))
|
180
|
+
end
|
181
|
+
|
182
|
+
context "with arbitrary object deserialization enabled by default" do
|
183
|
+
before :each do
|
184
|
+
YAML.enable_arbitrary_object_deserialization!
|
185
|
+
end
|
186
|
+
|
187
|
+
after :each do
|
188
|
+
YAML.disable_arbitrary_object_deserialization!
|
189
|
+
end
|
190
|
+
|
191
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
192
|
+
silence_warnings do
|
193
|
+
YAML.should_receive(:unsafe_load).with(*arguments)
|
194
|
+
YAML.load(*arguments)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
it "calls #safe_load if the :safe option is set to true" do
|
199
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
200
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "load_file" do
|
206
|
+
let(:filename) { "spec/exploit.1.9.2.yaml" } # doesn't really matter
|
207
|
+
|
208
|
+
it "issues a warning if the :safe option is omitted" do
|
209
|
+
silence_warnings do
|
210
|
+
Kernel.should_receive(:warn)
|
211
|
+
YAML.load_file(filename)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it "doesn't issue a warning as long as the :safe option is specified" do
|
216
|
+
Kernel.should_not_receive(:warn)
|
217
|
+
YAML.load_file(filename, :safe => true)
|
218
|
+
end
|
219
|
+
|
220
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
221
|
+
silence_warnings do
|
222
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
223
|
+
YAML.load_file(filename)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "calls #safe_load_file if the :safe option is set to true" do
|
228
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
229
|
+
YAML.load_file(filename, :safe => true)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "calls #unsafe_load_file if the :safe option is set to false" do
|
233
|
+
YAML.should_receive(:unsafe_load_file).with(filename)
|
234
|
+
YAML.load_file(filename, :safe => false)
|
235
|
+
end
|
236
|
+
|
237
|
+
context "with arbitrary object deserialization enabled by default" do
|
238
|
+
before :each do
|
239
|
+
YAML.enable_arbitrary_object_deserialization!
|
240
|
+
end
|
241
|
+
|
242
|
+
after :each do
|
243
|
+
YAML.disable_arbitrary_object_deserialization!
|
244
|
+
end
|
245
|
+
|
246
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
247
|
+
silence_warnings do
|
248
|
+
YAML.should_receive(:unsafe_load_file).with(filename)
|
249
|
+
YAML.load_file(filename)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
it "calls #safe_load if the :safe option is set to true" do
|
254
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
255
|
+
YAML.load_file(filename, :safe => true)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
135
259
|
end
|
data/spec/shared_specs.rb
CHANGED
@@ -70,6 +70,11 @@ module SharedSpecs
|
|
70
70
|
result.should == [nil] * 3
|
71
71
|
end
|
72
72
|
|
73
|
+
it "translates quoted empty strings to strings (not nil)" do
|
74
|
+
parse "foo: ''"
|
75
|
+
result.should == { "foo" => "" }
|
76
|
+
end
|
77
|
+
|
73
78
|
it "deals just fine with nested maps" do
|
74
79
|
parse <<-YAML
|
75
80
|
foo:
|
@@ -132,16 +137,19 @@ module SharedSpecs
|
|
132
137
|
result.should == { "time" => Time.new(2013, 1, 29, 5, 58, 0, "-08:00") }
|
133
138
|
end
|
134
139
|
|
135
|
-
it "applies the same transformation to keys" do
|
136
|
-
parse "2013-01-29 05:58:00 -0800: time"
|
137
|
-
result.should == { Time.new(2013, 1, 29, 5, 58, 0, "-08:00") => "time" }
|
138
|
-
end
|
139
|
-
|
140
140
|
it "applies the same transformation to elements in sequences" do
|
141
141
|
parse "- 2013-01-29 05:58:00 -0800"
|
142
142
|
result.should == [Time.new(2013, 1, 29, 5, 58, 0, "-08:00")]
|
143
143
|
end
|
144
144
|
|
145
|
+
# On Ruby 2.0.0-rc1, even YAML.load overflows the stack on this input.
|
146
|
+
if RUBY_VERSION != "2.0.0"
|
147
|
+
it "applies the same transformation to keys" do
|
148
|
+
parse "2013-01-29 05:58:00 -0800: time"
|
149
|
+
result.should == { Time.new(2013, 1, 29, 5, 58, 0, "-08:00") => "time" }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
145
153
|
else
|
146
154
|
it "does not deserialize times" do
|
147
155
|
parse "time: 2013-01-29 05:58:00 -0800"
|
@@ -152,7 +160,11 @@ module SharedSpecs
|
|
152
160
|
|
153
161
|
context "with symbol parsing enabled" do
|
154
162
|
before :each do
|
155
|
-
YAML.enable_symbol_parsing
|
163
|
+
YAML.enable_symbol_parsing!
|
164
|
+
end
|
165
|
+
|
166
|
+
after :each do
|
167
|
+
YAML.disable_symbol_parsing!
|
156
168
|
end
|
157
169
|
|
158
170
|
it "translates values starting with ':' to symbols" do
|
@@ -162,13 +174,11 @@ module SharedSpecs
|
|
162
174
|
|
163
175
|
it "applies the same transformation to keys" do
|
164
176
|
parse ":bar: symbol"
|
165
|
-
|
166
177
|
result.should == { :bar => "symbol" }
|
167
178
|
end
|
168
179
|
|
169
180
|
it "applies the same transformation to elements in sequences" do
|
170
181
|
parse "- :bar"
|
171
|
-
|
172
182
|
result.should == [:bar]
|
173
183
|
end
|
174
184
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe SafeYAML::Transform::ToDate do
|
4
|
+
it "returns true when the value matches a valid Date" do
|
5
|
+
subject.transform?("2013-01-01")[0].should == true
|
6
|
+
end
|
7
|
+
|
8
|
+
it "returns false when the value does not match a valid Date" do
|
9
|
+
subject.transform?("foobar").should be_false
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns false when the value does not end with a Date" do
|
13
|
+
subject.transform?("2013-01-01\nNOT A DATE").should be_false
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns false when the value does not begin with a Date" do
|
17
|
+
subject.transform?("NOT A DATE\n2013-01-01").should be_false
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe SafeYAML::Transform::ToFloat do
|
4
|
+
it "returns true when the value matches a valid Float" do
|
5
|
+
subject.transform?("20.00").should be_true
|
6
|
+
end
|
7
|
+
|
8
|
+
it "returns false when the value does not match a valid Float" do
|
9
|
+
subject.transform?("foobar").should be_false
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns false when the value spans multiple lines" do
|
13
|
+
subject.transform?("20.00\nNOT A FLOAT").should be_false
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe SafeYAML::Transform::ToInteger do
|
4
|
+
it "returns true when the value matches a valid Integer" do
|
5
|
+
subject.transform?("10")[0].should be_true
|
6
|
+
end
|
7
|
+
|
8
|
+
it "returns false when the value does not match a valid Integer" do
|
9
|
+
subject.transform?("foobar").should be_false
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns false when the value spans multiple lines" do
|
13
|
+
subject.transform?("10\nNOT AN INTEGER").should be_false
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe SafeYAML::Transform::ToSymbol do
|
4
|
+
def with_symbol_parsing_value(value)
|
5
|
+
symbol_parsing_flag = YAML.enable_symbol_parsing?
|
6
|
+
SafeYAML::OPTIONS[:enable_symbol_parsing] = value
|
7
|
+
|
8
|
+
yield
|
9
|
+
|
10
|
+
ensure
|
11
|
+
SafeYAML::OPTIONS[:enable_symbol_parsing] = symbol_parsing_flag
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_symbol_parsing(&block)
|
15
|
+
with_symbol_parsing_value(true, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def without_symbol_parsing(&block)
|
19
|
+
with_symbol_parsing_value(false, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns true when the value matches a valid Symbol" do
|
23
|
+
with_symbol_parsing { subject.transform?(":foo")[0].should be_true }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns false when symbol parsing is disabled" do
|
27
|
+
without_symbol_parsing { subject.transform?(":foo").should be_false }
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns false when the value does not match a valid Symbol" do
|
31
|
+
with_symbol_parsing { subject.transform?("foo").should be_false }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns false when the symbol does not begin the line" do
|
35
|
+
with_symbol_parsing do
|
36
|
+
subject.transform?("NOT A SYMBOL\n:foo").should be_false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns false when the symbol does not end the line" do
|
41
|
+
with_symbol_parsing do
|
42
|
+
subject.transform?(":foo\nNOT A SYMBOL").should be_false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe SafeYAML::Transform::ToTime do
|
4
|
+
# YAML don't parse times prior to 1.9.
|
5
|
+
if RUBY_VERSION >= "1.9.2"
|
6
|
+
it "returns true when the value matches a valid Time" do
|
7
|
+
subject.transform?("2013-01-01 10:00:00")[0].should == true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns false when the value does not match a valid Time" do
|
12
|
+
subject.transform?("not a time").should be_false
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns false when the beginning of a line does not match a Time" do
|
16
|
+
subject.transform?("NOT A TIME\n2013-01-01 10:00:00").should be_false
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_yaml
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,16 +9,19 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-02 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: Parse YAML safely, without that pesky arbitrary
|
14
|
+
description: Parse YAML safely, without that pesky arbitrary object deserialization
|
15
|
+
vulnerability
|
15
16
|
email: daniel.tao@gmail.com
|
16
17
|
executables: []
|
17
18
|
extensions: []
|
18
19
|
extra_rdoc_files: []
|
19
20
|
files:
|
20
21
|
- .gitignore
|
22
|
+
- .travis.yml
|
21
23
|
- Gemfile
|
24
|
+
- LICENSE.txt
|
22
25
|
- README.md
|
23
26
|
- Rakefile
|
24
27
|
- lib/safe_yaml.rb
|
@@ -43,8 +46,14 @@ files:
|
|
43
46
|
- spec/spec_helper.rb
|
44
47
|
- spec/support/exploitable_back_door.rb
|
45
48
|
- spec/syck_resolver_spec.rb
|
49
|
+
- spec/transform/to_date_spec.rb
|
50
|
+
- spec/transform/to_float_spec.rb
|
51
|
+
- spec/transform/to_integer_spec.rb
|
52
|
+
- spec/transform/to_symbol_spec.rb
|
53
|
+
- spec/transform/to_time_spec.rb
|
46
54
|
homepage: http://dtao.github.com/safe_yaml/
|
47
|
-
licenses:
|
55
|
+
licenses:
|
56
|
+
- MIT
|
48
57
|
post_install_message:
|
49
58
|
rdoc_options: []
|
50
59
|
require_paths:
|
@@ -63,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
72
|
version: '0'
|
64
73
|
requirements: []
|
65
74
|
rubyforge_project:
|
66
|
-
rubygems_version: 1.8.
|
75
|
+
rubygems_version: 1.8.25
|
67
76
|
signing_key:
|
68
77
|
specification_version: 3
|
69
78
|
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
@@ -77,3 +86,8 @@ test_files:
|
|
77
86
|
- spec/spec_helper.rb
|
78
87
|
- spec/support/exploitable_back_door.rb
|
79
88
|
- spec/syck_resolver_spec.rb
|
89
|
+
- spec/transform/to_date_spec.rb
|
90
|
+
- spec/transform/to_float_spec.rb
|
91
|
+
- spec/transform/to_integer_spec.rb
|
92
|
+
- spec/transform/to_symbol_spec.rb
|
93
|
+
- spec/transform/to_time_spec.rb
|