safe_yaml-instructure 0.8.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/.gitignore +2 -0
- data/.travis.yml +45 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +6 -0
- data/lib/safe_yaml.rb +145 -0
- data/lib/safe_yaml/psych_tag_verifier.rb +38 -0
- data/lib/safe_yaml/syck_tag_verifier.rb +27 -0
- data/lib/safe_yaml/tag_verifier.rb +23 -0
- data/lib/safe_yaml/version.rb +3 -0
- data/lib/safe_yaml/whitelist.rb +74 -0
- data/run_specs_all_ruby_versions.sh +21 -0
- data/safe_yaml.gemspec +24 -0
- data/spec/exploit.1.9.2.yaml +2 -0
- data/spec/exploit.1.9.3.yaml +2 -0
- data/spec/ok.yaml +3 -0
- data/spec/safe_yaml_spec.rb +541 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/exploitable_back_door.rb +29 -0
- data/spec/whitelist_spec.rb +46 -0
- data/spec/yaml_load_spec.rb +150 -0
- metadata +166 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module SafeYAML
|
2
|
+
class Whitelist
|
3
|
+
attr_reader :allowed
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
reset!
|
7
|
+
end
|
8
|
+
|
9
|
+
def check(tag, value)
|
10
|
+
@allowed.each do |ok, checker|
|
11
|
+
if ok === tag
|
12
|
+
check = check_value(ok, checker, value)
|
13
|
+
return check if check
|
14
|
+
end
|
15
|
+
end
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_value(tag, checker, value)
|
20
|
+
if checker == true
|
21
|
+
return :cacheable
|
22
|
+
end
|
23
|
+
|
24
|
+
if @cached[tag][value]
|
25
|
+
return :cacheable
|
26
|
+
end
|
27
|
+
|
28
|
+
result = checker.call(value)
|
29
|
+
if result == :cacheable
|
30
|
+
@cached[tag][value] = true
|
31
|
+
return :cacheable
|
32
|
+
elsif result
|
33
|
+
return :allowed
|
34
|
+
else
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset!
|
40
|
+
@allowed = {}
|
41
|
+
@cached = {}
|
42
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
43
|
+
# psych doesn't tag the default types, except for binary
|
44
|
+
add("!binary",
|
45
|
+
"tag:yaml.org,2002:binary")
|
46
|
+
else
|
47
|
+
add("tag:yaml.org,2002:str",
|
48
|
+
"tag:yaml.org,2002:int",
|
49
|
+
"tag:yaml.org,2002:float",
|
50
|
+
"tag:yaml.org,2002:binary",
|
51
|
+
"tag:yaml.org,2002:merge",
|
52
|
+
"tag:yaml.org,2002:null",
|
53
|
+
%r{^tag:yaml.org,2002:bool#},
|
54
|
+
%r{^tag:yaml.org,2002:float#},
|
55
|
+
%r{^tag:yaml.org,2002:timestamp#},
|
56
|
+
"tag:ruby.yaml.org,2002:object:YAML::Syck::BadAlias")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add(*tags, &block)
|
61
|
+
tags.each do |tag|
|
62
|
+
@cached[tag] = {} if block
|
63
|
+
@allowed[tag] = block || true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove(*tags)
|
68
|
+
tags.each do |tag|
|
69
|
+
@cached.delete(tag)
|
70
|
+
@allowed.delete(tag)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
[[ -s "$HOME/.rvm/scripts/rvm" ]] && . "$HOME/.rvm/scripts/rvm"
|
4
|
+
|
5
|
+
rvm use 1.8.7@safe_yaml
|
6
|
+
rake spec
|
7
|
+
|
8
|
+
rvm use 1.9.2@safe_yaml
|
9
|
+
YAMLER=syck rake spec
|
10
|
+
|
11
|
+
rvm use 1.9.3@safe_yaml
|
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
|
19
|
+
|
20
|
+
rvm use 2.0.0@safe_yaml
|
21
|
+
YAMLER=psych rake spec
|
data/safe_yaml.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.join(File.dirname(__FILE__), "lib", "safe_yaml", "version")
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "safe_yaml-instructure"
|
6
|
+
gem.version = SafeYAML::VERSION
|
7
|
+
gem.authors = ["Dan Tao", "Brian Palmer"]
|
8
|
+
gem.email = "brianp@instructure.com"
|
9
|
+
gem.description = %q{Parse YAML safely, without that pesky arbitrary object deserialization vulnerability}
|
10
|
+
gem.summary = %q{SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.}
|
11
|
+
gem.homepage = "http://github.com/instructure/safe_yaml/"
|
12
|
+
gem.license = "MIT"
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
|
17
|
+
gem.required_ruby_version = ">= 1.8.7"
|
18
|
+
|
19
|
+
gem.add_development_dependency "hashie"
|
20
|
+
gem.add_development_dependency "heredoc_unindent"
|
21
|
+
gem.add_development_dependency "rake"
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_development_dependency "travis-lint"
|
24
|
+
end
|
data/spec/ok.yaml
ADDED
@@ -0,0 +1,541 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require "exploitable_back_door"
|
4
|
+
|
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
|
+
|
14
|
+
before :each do
|
15
|
+
YAML.disable_symbol_parsing!
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "unsafe_load" do
|
19
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
20
|
+
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
21
|
+
backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
22
|
+
backdoor.should be_exploited_through_setter
|
23
|
+
end
|
24
|
+
|
25
|
+
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
26
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
27
|
+
backdoor.should be_exploited_through_init_with
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
32
|
+
backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
|
33
|
+
backdoor.should be_exploited_through_ivars
|
34
|
+
end
|
35
|
+
|
36
|
+
it "allows exploits through sequenced objects" do
|
37
|
+
yaml = <<-YAML.unindent
|
38
|
+
---
|
39
|
+
- 1
|
40
|
+
- 2
|
41
|
+
- !ruby/object:ExploitableBackDoor
|
42
|
+
foo: bar
|
43
|
+
YAML
|
44
|
+
array = YAML.unsafe_load(yaml)
|
45
|
+
array[2].should be_exploited_through_ivars
|
46
|
+
end
|
47
|
+
|
48
|
+
it "allows exploits through nested objects" do
|
49
|
+
yaml = <<-YAML.unindent
|
50
|
+
---
|
51
|
+
a: 1
|
52
|
+
b:
|
53
|
+
a: 1
|
54
|
+
b: !ruby/object:ExploitableBackDoor
|
55
|
+
foo: bar
|
56
|
+
YAML
|
57
|
+
hash = YAML.unsafe_load(yaml)
|
58
|
+
hash['b']['b'].should be_exploited_through_ivars
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "safe_load" do
|
63
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
64
|
+
expect { YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n") }.to raise_error(SafeYAML::UnsafeTagError)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
68
|
+
expect { YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n") }.to raise_error(SafeYAML::UnsafeTagError)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "does NOT allow exploits in sequenced objects" do
|
72
|
+
yaml = <<-YAML.unindent
|
73
|
+
---
|
74
|
+
- 1
|
75
|
+
- 2
|
76
|
+
- !ruby/object:ExploitableBackDoor
|
77
|
+
foo: bar
|
78
|
+
YAML
|
79
|
+
expect { YAML.safe_load(yaml) }.to raise_error(SafeYAML::UnsafeTagError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "does NOT allow exploits in nested objects" do
|
83
|
+
yaml = <<-YAML.unindent
|
84
|
+
---
|
85
|
+
a: 1
|
86
|
+
b:
|
87
|
+
a: 1
|
88
|
+
b: !ruby/object:ExploitableBackDoor
|
89
|
+
foo: bar
|
90
|
+
YAML
|
91
|
+
expect { YAML.safe_load(yaml) }.to raise_error(SafeYAML::UnsafeTagError)
|
92
|
+
end
|
93
|
+
|
94
|
+
context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
|
95
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
96
|
+
let(:arguments) {
|
97
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
98
|
+
["foo: bar", nil]
|
99
|
+
else
|
100
|
+
["foo: bar"]
|
101
|
+
end
|
102
|
+
}
|
103
|
+
it "uses Psych internally to parse YAML" do
|
104
|
+
Psych::Parser.any_instance.should_receive(:parse).with(*arguments)
|
105
|
+
YAML.should_receive(:unsafe_load)
|
106
|
+
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
107
|
+
YAML.safe_load(*arguments)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if SafeYAML::YAML_ENGINE == "syck"
|
112
|
+
it "uses Syck internally to parse YAML" do
|
113
|
+
YAML.should_receive(:parse).with("foo: bar")
|
114
|
+
# This won't work now; we just want to ensure YAML::parse was in fact called.
|
115
|
+
YAML.safe_load("foo: bar") rescue nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "loads a plain ol' YAML document just fine" do
|
121
|
+
YAML.enable_symbol_parsing!
|
122
|
+
result = YAML.safe_load <<-YAML.unindent
|
123
|
+
foo:
|
124
|
+
number: 1
|
125
|
+
float: 1.5
|
126
|
+
string: Hello, there!
|
127
|
+
symbol: :blah
|
128
|
+
quoted_symbol_1: ":blah"
|
129
|
+
quoted_symbol_2: ':blah'
|
130
|
+
y: true
|
131
|
+
n: false
|
132
|
+
nada:
|
133
|
+
date: 2013-02-14
|
134
|
+
time: 2013-02-14 09:49:19.419780000 -07:00
|
135
|
+
sequence:
|
136
|
+
- hi
|
137
|
+
- bye
|
138
|
+
YAML
|
139
|
+
|
140
|
+
result.should == {
|
141
|
+
"foo" => {
|
142
|
+
"number" => 1,
|
143
|
+
"float" => 1.5,
|
144
|
+
"string" => "Hello, there!",
|
145
|
+
"symbol" => :blah,
|
146
|
+
"quoted_symbol_1" => ":blah",
|
147
|
+
"quoted_symbol_2" => ":blah",
|
148
|
+
"y" => true,
|
149
|
+
"n" => false,
|
150
|
+
"nada" => nil,
|
151
|
+
"date" => YAML.unsafe_load("2013-02-14"),
|
152
|
+
"time" => YAML.unsafe_load("2013-02-14 09:49:19.419780000 -07:00"),
|
153
|
+
"sequence" => ["hi", "bye"]
|
154
|
+
}
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
it "works for YAML documents with anchors and aliases" do
|
159
|
+
result = YAML.safe_load <<-YAML
|
160
|
+
- &id001 {}
|
161
|
+
- *id001
|
162
|
+
- *id001
|
163
|
+
YAML
|
164
|
+
|
165
|
+
result.should == [{}, {}, {}]
|
166
|
+
end
|
167
|
+
|
168
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
169
|
+
# syck doesn't support !binary
|
170
|
+
it "works for YAML documents with binary tagged keys" do
|
171
|
+
result = YAML.safe_load <<-YAML
|
172
|
+
? !!binary >
|
173
|
+
Zm9v
|
174
|
+
: "bar"
|
175
|
+
? !!binary >
|
176
|
+
YmFy
|
177
|
+
: "baz"
|
178
|
+
YAML
|
179
|
+
|
180
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
181
|
+
end
|
182
|
+
|
183
|
+
it "works for YAML documents with binary tagged values" do
|
184
|
+
result = YAML.safe_load <<-YAML
|
185
|
+
"foo": !!binary >
|
186
|
+
YmFy
|
187
|
+
"bar": !!binary >
|
188
|
+
YmF6
|
189
|
+
YAML
|
190
|
+
|
191
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "works for YAML documents with binary tagged array values" do
|
195
|
+
result = YAML.safe_load <<-YAML
|
196
|
+
- !binary |-
|
197
|
+
Zm9v
|
198
|
+
- !binary |-
|
199
|
+
YmFy
|
200
|
+
YAML
|
201
|
+
|
202
|
+
result.should == ["foo", "bar"]
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "works for YAML documents with sections" do
|
207
|
+
result = YAML.safe_load <<-YAML
|
208
|
+
mysql: &mysql
|
209
|
+
adapter: mysql
|
210
|
+
pool: 30
|
211
|
+
login: &login
|
212
|
+
username: user
|
213
|
+
password: password123
|
214
|
+
development: &development
|
215
|
+
<<: *mysql
|
216
|
+
<<: *login
|
217
|
+
host: localhost
|
218
|
+
YAML
|
219
|
+
|
220
|
+
result.should == {
|
221
|
+
"mysql" => {
|
222
|
+
"adapter" => "mysql",
|
223
|
+
"pool" => 30
|
224
|
+
},
|
225
|
+
"login" => {
|
226
|
+
"username" => "user",
|
227
|
+
"password" => "password123"
|
228
|
+
},
|
229
|
+
"development" => {
|
230
|
+
"adapter" => "mysql",
|
231
|
+
"pool" => 30,
|
232
|
+
"username" => "user",
|
233
|
+
"password" => "password123",
|
234
|
+
"host" => "localhost"
|
235
|
+
}
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
it "correctly prefers explicitly defined values over default values from included sections" do
|
240
|
+
# Repeating this test 100 times to increase the likelihood of running into an issue caused by
|
241
|
+
# non-deterministic hash key enumeration.
|
242
|
+
100.times do
|
243
|
+
result = YAML.safe_load <<-YAML
|
244
|
+
defaults: &defaults
|
245
|
+
foo: foo
|
246
|
+
bar: bar
|
247
|
+
baz: baz
|
248
|
+
custom:
|
249
|
+
<<: *defaults
|
250
|
+
bar: custom_bar
|
251
|
+
baz: custom_baz
|
252
|
+
YAML
|
253
|
+
|
254
|
+
result["custom"].should == {
|
255
|
+
"foo" => "foo",
|
256
|
+
"bar" => "custom_bar",
|
257
|
+
"baz" => "custom_baz"
|
258
|
+
}
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it "works with multi-level inheritance" do
|
263
|
+
result = YAML.safe_load <<-YAML
|
264
|
+
defaults: &defaults
|
265
|
+
foo: foo
|
266
|
+
bar: bar
|
267
|
+
baz: baz
|
268
|
+
custom: &custom
|
269
|
+
<<: *defaults
|
270
|
+
bar: custom_bar
|
271
|
+
baz: custom_baz
|
272
|
+
grandcustom: &grandcustom
|
273
|
+
<<: *custom
|
274
|
+
YAML
|
275
|
+
|
276
|
+
result.should == {
|
277
|
+
"defaults" => { "foo" => "foo", "bar" => "bar", "baz" => "baz" },
|
278
|
+
"custom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" },
|
279
|
+
"grandcustom" => { "foo" => "foo", "bar" => "custom_bar", "baz" => "custom_baz" }
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
context "with custom whitelist defined" do
|
284
|
+
class MyClass
|
285
|
+
attr_reader :a, :b
|
286
|
+
def initialize(a, b)
|
287
|
+
@a = a
|
288
|
+
@b = b
|
289
|
+
end
|
290
|
+
|
291
|
+
def self.new_from_hash(hash)
|
292
|
+
self.new(*hash.values_at('a', 'b'))
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
before :each do
|
297
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
298
|
+
YAML.whitelist.add('!ruby/object:Set',
|
299
|
+
'!map:Hashie::Mash',
|
300
|
+
'!ruby/object:MyClass')
|
301
|
+
YAML.whitelist.add('!ruby/class') { |val| val == 'A' }
|
302
|
+
else
|
303
|
+
YAML.whitelist.add('tag:ruby.yaml.org,2002:object:Set',
|
304
|
+
'tag:yaml.org,2002:map:Hashie::Mash',
|
305
|
+
'tag:ruby.yaml.org,2002:object:MyClass')
|
306
|
+
YAML.whitelist.add('tag:ruby.yaml.org,2002:class') { |val| val == 'A' }
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
after :each do
|
311
|
+
YAML.whitelist.reset!
|
312
|
+
end
|
313
|
+
|
314
|
+
it "will allow array-structure classes via the whitelist" do
|
315
|
+
result = YAML.safe_load <<-YAML.unindent
|
316
|
+
--- !ruby/object:Set
|
317
|
+
hash:
|
318
|
+
1: true
|
319
|
+
2: true
|
320
|
+
3: true
|
321
|
+
YAML
|
322
|
+
|
323
|
+
result.should be_a(Set)
|
324
|
+
result.to_a.should =~ [1, 2, 3]
|
325
|
+
end
|
326
|
+
|
327
|
+
it "will allow hash-structure classes via the whitelist" do
|
328
|
+
pending("1.9.2 psych doesn't load the map class") if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION == "1.9.2"
|
329
|
+
result = YAML.safe_load <<-YAML.unindent
|
330
|
+
--- !map:Hashie::Mash
|
331
|
+
foo: bar
|
332
|
+
YAML
|
333
|
+
|
334
|
+
result.should be_a(Hashie::Mash)
|
335
|
+
result.to_hash.should == { "foo" => "bar" }
|
336
|
+
|
337
|
+
result = YAML.safe_load <<-YAML.unindent
|
338
|
+
--- !ruby/object:MyClass
|
339
|
+
a: 1
|
340
|
+
b: !ruby/object:MyClass
|
341
|
+
a: &70346695583400 !ruby/object:MyClass
|
342
|
+
a: 2
|
343
|
+
b: *70346695583400
|
344
|
+
b: *70346695583400
|
345
|
+
YAML
|
346
|
+
|
347
|
+
result.should be_a(MyClass)
|
348
|
+
result.a.should == 1
|
349
|
+
result.b.should be_a(MyClass)
|
350
|
+
result.b.a.should be_a(MyClass)
|
351
|
+
result.b.a.a.should == 2
|
352
|
+
result.b.a.b.object_id.should == result.b.a.object_id
|
353
|
+
result.b.a.object_id.should == result.b.b.object_id
|
354
|
+
end
|
355
|
+
|
356
|
+
class A; end
|
357
|
+
class B; end
|
358
|
+
|
359
|
+
it "allows a block in the whitelist to determine safety" do
|
360
|
+
res = YAML.safe_load <<-YAML.unindent
|
361
|
+
---
|
362
|
+
a: !ruby/class A
|
363
|
+
YAML
|
364
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
365
|
+
res['a'].should == A
|
366
|
+
end
|
367
|
+
|
368
|
+
yaml = <<-YAML.unindent
|
369
|
+
---
|
370
|
+
a: !ruby/class A
|
371
|
+
b: !ruby/class B
|
372
|
+
YAML
|
373
|
+
|
374
|
+
expect { YAML.safe_load yaml }.to raise_error(SafeYAML::UnsafeTagError)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe "unsafe_load_file" do
|
380
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
381
|
+
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
382
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
|
383
|
+
backdoor.should be_exploited_through_setter
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
|
388
|
+
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
389
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
390
|
+
backdoor.should be_exploited_through_init_with
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
|
395
|
+
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
396
|
+
backdoor.should be_exploited_through_ivars
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
describe "safe_load_file" do
|
401
|
+
it "loads successfully" do
|
402
|
+
result = YAML.safe_load_file "spec/ok.yaml"
|
403
|
+
result.should == { 'a' => 1, 'b' => 2 }
|
404
|
+
end
|
405
|
+
|
406
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
407
|
+
expect { YAML.safe_load_file "spec/exploit.1.9.3.yaml" }.to raise_error(SafeYAML::UnsafeTagError)
|
408
|
+
end
|
409
|
+
|
410
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
411
|
+
expect { YAML.safe_load_file "spec/exploit.1.9.2.yaml" }.to raise_error(SafeYAML::UnsafeTagError)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe "load" do
|
416
|
+
let(:arguments) {
|
417
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
418
|
+
["foo: bar", nil]
|
419
|
+
else
|
420
|
+
["foo: bar"]
|
421
|
+
end
|
422
|
+
}
|
423
|
+
|
424
|
+
context "with :suppress_warnings set to true" do
|
425
|
+
before :each do SafeYAML::OPTIONS[:suppress_warnings] = true; end
|
426
|
+
after :each do SafeYAML::OPTIONS[:suppress_warnings] = false; end
|
427
|
+
|
428
|
+
it "doesn't issue a warning if :suppress_warnings option is set to true" do
|
429
|
+
SafeYAML::OPTIONS[:suppress_warnings] = true
|
430
|
+
Kernel.should_not_receive(:warn)
|
431
|
+
YAML.load(*arguments)
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
it "issues a warning if the :safe option is omitted" do
|
436
|
+
silence_warnings do
|
437
|
+
Kernel.should_receive(:warn)
|
438
|
+
YAML.load(*arguments)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
it "doesn't issue a warning as long as the :safe option is specified" do
|
443
|
+
Kernel.should_not_receive(:warn)
|
444
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
445
|
+
end
|
446
|
+
|
447
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
448
|
+
silence_warnings do
|
449
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
450
|
+
YAML.load(*arguments)
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
it "calls #safe_load if the :safe option is set to true" do
|
455
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
456
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
457
|
+
end
|
458
|
+
|
459
|
+
it "calls #unsafe_load if the :safe option is set to false" do
|
460
|
+
YAML.should_receive(:unsafe_load).with(*arguments)
|
461
|
+
YAML.load(*(arguments + [{:safe => false}]))
|
462
|
+
end
|
463
|
+
|
464
|
+
context "with arbitrary object deserialization enabled by default" do
|
465
|
+
before :each do
|
466
|
+
YAML.enable_arbitrary_object_deserialization!
|
467
|
+
end
|
468
|
+
|
469
|
+
after :each do
|
470
|
+
YAML.disable_arbitrary_object_deserialization!
|
471
|
+
end
|
472
|
+
|
473
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
474
|
+
silence_warnings do
|
475
|
+
YAML.should_receive(:unsafe_load).with(*arguments)
|
476
|
+
YAML.load(*arguments)
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
it "calls #safe_load if the :safe option is set to true" do
|
481
|
+
YAML.should_receive(:safe_load).with(*arguments)
|
482
|
+
YAML.load(*(arguments + [{:safe => true}]))
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
describe "load_file" do
|
488
|
+
let(:filename) { "spec/ok.yaml" } # doesn't really matter
|
489
|
+
|
490
|
+
it "issues a warning if the :safe option is omitted" do
|
491
|
+
silence_warnings do
|
492
|
+
Kernel.should_receive(:warn)
|
493
|
+
YAML.load_file(filename)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
it "doesn't issue a warning as long as the :safe option is specified" do
|
498
|
+
Kernel.should_not_receive(:warn)
|
499
|
+
YAML.load_file(filename, :safe => true)
|
500
|
+
end
|
501
|
+
|
502
|
+
it "defaults to safe mode if the :safe option is omitted" do
|
503
|
+
silence_warnings do
|
504
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
505
|
+
YAML.load_file(filename)
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
it "calls #safe_load_file if the :safe option is set to true" do
|
510
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
511
|
+
YAML.load_file(filename, :safe => true)
|
512
|
+
end
|
513
|
+
|
514
|
+
it "calls #unsafe_load_file if the :safe option is set to false" do
|
515
|
+
YAML.should_receive(:unsafe_load_file).with(filename)
|
516
|
+
YAML.load_file(filename, :safe => false)
|
517
|
+
end
|
518
|
+
|
519
|
+
context "with arbitrary object deserialization enabled by default" do
|
520
|
+
before :each do
|
521
|
+
YAML.enable_arbitrary_object_deserialization!
|
522
|
+
end
|
523
|
+
|
524
|
+
after :each do
|
525
|
+
YAML.disable_arbitrary_object_deserialization!
|
526
|
+
end
|
527
|
+
|
528
|
+
it "defaults to unsafe mode if the :safe option is omitted" do
|
529
|
+
silence_warnings do
|
530
|
+
YAML.should_receive(:unsafe_load_file).with(filename)
|
531
|
+
YAML.load_file(filename)
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
it "calls #safe_load if the :safe option is set to true" do
|
536
|
+
YAML.should_receive(:safe_load_file).with(filename)
|
537
|
+
YAML.load_file(filename, :safe => true)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|