safe_yaml 0.6.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +5 -2
- data/lib/safe_yaml.rb +31 -48
- data/lib/safe_yaml/psych_handler.rb +4 -3
- data/lib/safe_yaml/syck_resolver.rb +1 -1
- data/lib/safe_yaml/transform.rb +14 -1
- data/lib/safe_yaml/version.rb +1 -1
- data/run_specs_all_ruby_versions.sh +9 -3
- data/spec/psych_handler_spec.rb +1 -1
- data/spec/safe_yaml_spec.rb +56 -3
- data/spec/shared_specs.rb +1 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/syck_resolver_spec.rb +1 -1
- data/spec/transform/to_time_spec.rb +1 -1
- metadata +41 -23
data/README.md
CHANGED
@@ -99,14 +99,15 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
|
|
99
99
|
- Booleans
|
100
100
|
- Nils
|
101
101
|
|
102
|
-
Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing
|
102
|
+
Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!` (for example, in an initializer).
|
103
103
|
|
104
104
|
Known Issues
|
105
105
|
------------
|
106
106
|
|
107
107
|
Also note that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
|
108
108
|
|
109
|
-
- **Guard
|
109
|
+
- [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so calling `YAML.enable_symbol_parsing!` is necessary to allow Guard to work.
|
110
|
+
- [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so calling `YAML.enable_symbol_parsing!` should allow it to work.
|
110
111
|
|
111
112
|
The above list will grow over time, as more issues are discovered.
|
112
113
|
|
@@ -119,3 +120,5 @@ Requirements
|
|
119
120
|
------------
|
120
121
|
|
121
122
|
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).
|
123
|
+
|
124
|
+
If you are using a version of Ruby where Psych is the default YAML engine (e.g., 1.9.3) but you want to use Syck, be sure to set `YAML::ENGINE.yamler = "syck"` **before** requiring the safe_yaml gem.
|
data/lib/safe_yaml.rb
CHANGED
@@ -10,6 +10,9 @@ require "safe_yaml/transform"
|
|
10
10
|
require "safe_yaml/version"
|
11
11
|
|
12
12
|
module SafeYAML
|
13
|
+
MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
|
14
|
+
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
|
15
|
+
|
13
16
|
OPTIONS = {
|
14
17
|
:enable_symbol_parsing => false,
|
15
18
|
:enable_arbitrary_object_deserialization => false,
|
@@ -18,14 +21,11 @@ module SafeYAML
|
|
18
21
|
end
|
19
22
|
|
20
23
|
module YAML
|
21
|
-
def self.load_with_options(yaml,
|
24
|
+
def self.load_with_options(yaml, *filename_and_options)
|
25
|
+
options = filename_and_options.last || {}
|
22
26
|
safe_mode = safe_mode_from_options("load", options)
|
23
|
-
|
24
27
|
arguments = [yaml]
|
25
|
-
|
26
|
-
arguments << options[:filename]
|
27
|
-
end
|
28
|
-
|
28
|
+
arguments << filename_and_options.first if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
29
29
|
safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
|
30
30
|
end
|
31
31
|
|
@@ -34,15 +34,16 @@ module YAML
|
|
34
34
|
safe_mode ? safe_load_file(file) : unsafe_load_file(file)
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
load_with_options(yaml, options.merge(:filename => filename))
|
39
|
-
end
|
40
|
-
|
41
|
-
if RUBY_VERSION >= "1.9.3"
|
37
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
42
38
|
require "safe_yaml/psych_handler"
|
43
39
|
def self.safe_load(yaml, filename=nil)
|
44
40
|
safe_handler = SafeYAML::PsychHandler.new
|
45
|
-
Psych::Parser.new(safe_handler)
|
41
|
+
parser = Psych::Parser.new(safe_handler)
|
42
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
43
|
+
parser.parse(yaml, filename)
|
44
|
+
else
|
45
|
+
parser.parse(yaml)
|
46
|
+
end
|
46
47
|
return safe_handler.result
|
47
48
|
end
|
48
49
|
|
@@ -51,25 +52,13 @@ module YAML
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def self.unsafe_load_file(filename)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
safe_handler = SafeYAML::PsychHandler.new
|
62
|
-
Psych::Parser.new(safe_handler).parse(yaml)
|
63
|
-
return safe_handler.result
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.safe_load_file(filename)
|
67
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f }
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.unsafe_load_file(filename)
|
71
|
-
# https://github.com/tenderlove/psych/blob/v1.2.0/lib/psych.rb#L228-230
|
72
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f }
|
55
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
56
|
+
# https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
|
57
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
|
58
|
+
else
|
59
|
+
# https://github.com/tenderlove/psych/blob/v1.2.2/lib/psych.rb#L231-233
|
60
|
+
self.unsafe_load File.open(filename)
|
61
|
+
end
|
73
62
|
end
|
74
63
|
|
75
64
|
else
|
@@ -92,13 +81,7 @@ module YAML
|
|
92
81
|
|
93
82
|
class << self
|
94
83
|
alias_method :unsafe_load, :load
|
95
|
-
|
96
|
-
if RUBY_VERSION >= "1.9.3"
|
97
|
-
alias_method :load, :load_with_filename_and_options
|
98
|
-
else
|
99
|
-
alias_method :load, :load_with_options
|
100
|
-
end
|
101
|
-
|
84
|
+
alias_method :load, :load_with_options
|
102
85
|
alias_method :load_file, :load_file_with_options
|
103
86
|
|
104
87
|
def enable_symbol_parsing?
|
@@ -124,18 +107,18 @@ module YAML
|
|
124
107
|
def disable_arbitrary_object_deserialization!
|
125
108
|
SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = false
|
126
109
|
end
|
127
|
-
end
|
128
110
|
|
129
|
-
|
130
|
-
|
131
|
-
|
111
|
+
private
|
112
|
+
def safe_mode_from_options(method, options={})
|
113
|
+
safe_mode = options[:safe]
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
115
|
+
if safe_mode.nil?
|
116
|
+
mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
|
117
|
+
Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode." unless SafeYAML::OPTIONS[:suppress_warnings]
|
118
|
+
safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
|
119
|
+
end
|
138
120
|
|
139
|
-
|
121
|
+
safe_mode
|
122
|
+
end
|
140
123
|
end
|
141
124
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "psych"
|
2
|
+
require "base64"
|
2
3
|
|
3
4
|
module SafeYAML
|
4
5
|
class PsychHandler < Psych::Handler
|
@@ -13,8 +14,8 @@ module SafeYAML
|
|
13
14
|
@result
|
14
15
|
end
|
15
16
|
|
16
|
-
def add_to_current_structure(value, anchor=nil, quoted=nil)
|
17
|
-
value = Transform.to_proper_type(value, quoted)
|
17
|
+
def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
|
18
|
+
value = Transform.to_proper_type(value, quoted, tag)
|
18
19
|
|
19
20
|
@anchors[anchor] = value if anchor
|
20
21
|
|
@@ -62,7 +63,7 @@ module SafeYAML
|
|
62
63
|
end
|
63
64
|
|
64
65
|
def scalar(value, anchor, tag, plain, quoted, style)
|
65
|
-
add_to_current_structure(value, anchor, quoted)
|
66
|
+
add_to_current_structure(value, anchor, quoted, tag)
|
66
67
|
end
|
67
68
|
|
68
69
|
def start_mapping(anchor, tag, implicit, style)
|
@@ -41,7 +41,7 @@ module SafeYAML
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def resolve_scalar(node)
|
44
|
-
Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)))
|
44
|
+
Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)), node.type_id)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/lib/safe_yaml/transform.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module SafeYAML
|
2
4
|
class Transform
|
3
5
|
TRANSFORMERS = [
|
@@ -10,7 +12,7 @@ module SafeYAML
|
|
10
12
|
Transform::ToTime.new
|
11
13
|
]
|
12
14
|
|
13
|
-
def self.
|
15
|
+
def self.to_guessed_type(value, quoted=false)
|
14
16
|
return value if quoted
|
15
17
|
|
16
18
|
if value.is_a?(String)
|
@@ -22,5 +24,16 @@ module SafeYAML
|
|
22
24
|
|
23
25
|
value
|
24
26
|
end
|
27
|
+
|
28
|
+
def self.to_proper_type(value, quoted=false,tag=nil)
|
29
|
+
case tag
|
30
|
+
when "tag:yaml.org,2002:binary"
|
31
|
+
return Base64.decode64(value)
|
32
|
+
when "x-private:binary"
|
33
|
+
return Base64.decode64(value)
|
34
|
+
else
|
35
|
+
return self.to_guessed_type(value, quoted)
|
36
|
+
end
|
37
|
+
end
|
25
38
|
end
|
26
39
|
end
|
data/lib/safe_yaml/version.rb
CHANGED
@@ -6,10 +6,16 @@ rvm use 1.8.7@safe_yaml
|
|
6
6
|
rake spec
|
7
7
|
|
8
8
|
rvm use 1.9.2@safe_yaml
|
9
|
-
rake spec
|
9
|
+
YAMLER=syck rake spec
|
10
10
|
|
11
11
|
rvm use 1.9.3@safe_yaml
|
12
|
-
rake spec
|
12
|
+
YAMLER=syck rake spec
|
13
|
+
|
14
|
+
rvm use 1.9.2@safe_yaml
|
15
|
+
YAMLER=psych rake spec
|
16
|
+
|
17
|
+
rvm use 1.9.3@safe_yaml
|
18
|
+
YAMLER=psych rake spec
|
13
19
|
|
14
20
|
rvm use 2.0.0@safe_yaml
|
15
|
-
rake spec
|
21
|
+
YAMLER=psych rake spec
|
data/spec/psych_handler_spec.rb
CHANGED
data/spec/safe_yaml_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe YAML do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "unsafe_load" do
|
19
|
-
if RUBY_VERSION >= "1.9.3"
|
19
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
20
20
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
21
21
|
backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
22
22
|
backdoor.should be_exploited_through_setter
|
@@ -45,6 +45,33 @@ describe YAML do
|
|
45
45
|
object.should_not be_a(ExploitableBackDoor)
|
46
46
|
end
|
47
47
|
|
48
|
+
context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
|
49
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
50
|
+
let(:arguments) {
|
51
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
52
|
+
["foo: bar", nil]
|
53
|
+
else
|
54
|
+
["foo: bar"]
|
55
|
+
end
|
56
|
+
}
|
57
|
+
it "uses Psych internally to parse YAML" do
|
58
|
+
stub_parser = stub(Psych::Parser)
|
59
|
+
Psych::Parser.stub(:new).and_return(stub_parser)
|
60
|
+
stub_parser.should_receive(:parse).with(*arguments)
|
61
|
+
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
62
|
+
YAML.safe_load(*arguments)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if SafeYAML::YAML_ENGINE == "syck"
|
67
|
+
it "uses Syck internally to parse YAML" do
|
68
|
+
YAML.should_receive(:parse).with("foo: bar")
|
69
|
+
# This won't work now; we just want to ensure YAML::parse was in fact called.
|
70
|
+
YAML.safe_load("foo: bar") rescue nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
48
75
|
it "loads a plain ol' YAML document just fine" do
|
49
76
|
result = YAML.safe_load <<-YAML.unindent
|
50
77
|
foo:
|
@@ -76,6 +103,30 @@ describe YAML do
|
|
76
103
|
result.should == [{}, {}, {}]
|
77
104
|
end
|
78
105
|
|
106
|
+
it "works for YAML documents with binary tagged keys" do
|
107
|
+
result = YAML.safe_load <<-YAML
|
108
|
+
? !!binary >
|
109
|
+
Zm9v
|
110
|
+
: "bar"
|
111
|
+
? !!binary >
|
112
|
+
YmFy
|
113
|
+
: "baz"
|
114
|
+
YAML
|
115
|
+
|
116
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "works for YAML documents with binary tagged values" do
|
120
|
+
result = YAML.safe_load <<-YAML
|
121
|
+
"foo": !!binary >
|
122
|
+
YmFy
|
123
|
+
"bar": !!binary >
|
124
|
+
YmF6
|
125
|
+
YAML
|
126
|
+
|
127
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
128
|
+
end
|
129
|
+
|
79
130
|
it "works for YAML documents with sections" do
|
80
131
|
result = YAML.safe_load <<-YAML
|
81
132
|
mysql: &mysql
|
@@ -134,12 +185,14 @@ describe YAML do
|
|
134
185
|
end
|
135
186
|
|
136
187
|
describe "unsafe_load_file" do
|
137
|
-
if RUBY_VERSION >= "1.9.3"
|
188
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
138
189
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
139
190
|
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
|
140
191
|
backdoor.should be_exploited_through_setter
|
141
192
|
end
|
193
|
+
end
|
142
194
|
|
195
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
|
143
196
|
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
144
197
|
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
145
198
|
backdoor.should be_exploited_through_init_with
|
@@ -166,7 +219,7 @@ describe YAML do
|
|
166
219
|
|
167
220
|
describe "load" do
|
168
221
|
let (:arguments) {
|
169
|
-
if
|
222
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
170
223
|
["foo: bar", nil]
|
171
224
|
else
|
172
225
|
["foo: bar"]
|
data/spec/shared_specs.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,12 @@ ROOT = File.join(HERE, "..")
|
|
4
4
|
$LOAD_PATH << File.join(ROOT, "lib")
|
5
5
|
$LOAD_PATH << File.join(HERE, "support")
|
6
6
|
|
7
|
+
if ENV["YAMLER"]
|
8
|
+
require "yaml"
|
9
|
+
YAML::ENGINE.yamler = ENV["YAMLER"]
|
10
|
+
puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
|
11
|
+
end
|
12
|
+
|
7
13
|
require "safe_yaml"
|
8
14
|
require "heredoc_unindent"
|
9
15
|
|
data/spec/syck_resolver_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
2
|
|
3
3
|
describe SafeYAML::Transform::ToTime do
|
4
|
-
#
|
4
|
+
# It seems both Psych and Syck parse times starting w/ Ruby 1.9.2.
|
5
5
|
if RUBY_VERSION >= "1.9.2"
|
6
6
|
it "returns true when the value matches a valid Time" do
|
7
7
|
subject.transform?("2013-01-01 10:00:00")[0].should == true
|
metadata
CHANGED
@@ -1,23 +1,32 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe_yaml
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 3
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 7
|
9
|
+
- 0
|
10
|
+
version: 0.7.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Dan Tao
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
17
|
+
|
18
|
+
date: 2013-02-08 00:00:00 Z
|
13
19
|
dependencies: []
|
14
|
-
|
15
|
-
|
20
|
+
|
21
|
+
description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
|
16
22
|
email: daniel.tao@gmail.com
|
17
23
|
executables: []
|
24
|
+
|
18
25
|
extensions: []
|
26
|
+
|
19
27
|
extra_rdoc_files: []
|
20
|
-
|
28
|
+
|
29
|
+
files:
|
21
30
|
- .gitignore
|
22
31
|
- .travis.yml
|
23
32
|
- Gemfile
|
@@ -52,32 +61,41 @@ files:
|
|
52
61
|
- spec/transform/to_symbol_spec.rb
|
53
62
|
- spec/transform/to_time_spec.rb
|
54
63
|
homepage: http://dtao.github.com/safe_yaml/
|
55
|
-
licenses:
|
64
|
+
licenses:
|
56
65
|
- MIT
|
57
66
|
post_install_message:
|
58
67
|
rdoc_options: []
|
59
|
-
|
68
|
+
|
69
|
+
require_paths:
|
60
70
|
- lib
|
61
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
72
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 57
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 8
|
80
|
+
- 7
|
66
81
|
version: 1.8.7
|
67
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
83
|
none: false
|
69
|
-
requirements:
|
70
|
-
- -
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
73
91
|
requirements: []
|
92
|
+
|
74
93
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.8.
|
94
|
+
rubygems_version: 1.8.25
|
76
95
|
signing_key:
|
77
96
|
specification_version: 3
|
78
|
-
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
79
|
-
|
80
|
-
test_files:
|
97
|
+
summary: SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.
|
98
|
+
test_files:
|
81
99
|
- spec/exploit.1.9.2.yaml
|
82
100
|
- spec/exploit.1.9.3.yaml
|
83
101
|
- spec/psych_handler_spec.rb
|