safe_yaml 0.8.0 → 0.8.1
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 +9 -0
- data/lib/safe_yaml.rb +8 -7
- data/lib/safe_yaml/psych_resolver.rb +1 -1
- data/lib/safe_yaml/resolver.rb +14 -5
- data/lib/safe_yaml/safe_to_ruby_visitor.rb +17 -0
- data/lib/safe_yaml/syck_node_monkeypatch.rb +5 -1
- data/lib/safe_yaml/version.rb +1 -1
- data/spec/resolver_specs.rb +1 -1
- data/spec/safe_yaml_spec.rb +52 -7
- metadata +23 -41
- data/lib/safe_yaml/psych_visitor.rb +0 -13
data/README.md
CHANGED
@@ -133,6 +133,15 @@ EOYAML
|
|
133
133
|
> YAML.safe_load(yaml)
|
134
134
|
=> #<OpenStruct :backdoor={"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}>
|
135
135
|
|
136
|
+
You may prefer, rather than quietly sanitizing and accepting YAML documents with unknown tags, to fail loudly when questionable data is encountered. In this case, you can also set the `:raise_on_unknown_tag` option to `true`:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
|
140
|
+
```
|
141
|
+
|
142
|
+
> YAML.safe_load(yaml)
|
143
|
+
=> RuntimeError: Unknown YAML tag '!ruby/hash:ExploitableClassBuilder'
|
144
|
+
|
136
145
|
Pretty sweet, right?
|
137
146
|
|
138
147
|
Known Issues
|
data/lib/safe_yaml.rb
CHANGED
@@ -14,17 +14,18 @@ module SafeYAML
|
|
14
14
|
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
|
15
15
|
|
16
16
|
DEFAULT_OPTIONS = {
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
17
|
+
:default_mode => nil,
|
18
|
+
:deserialize_symbols => false,
|
19
|
+
:whitelisted_tags => [],
|
20
|
+
:custom_initializers => {},
|
21
|
+
:raise_on_unknown_tag => false
|
21
22
|
}.freeze
|
22
23
|
|
23
24
|
OPTIONS = DEFAULT_OPTIONS.dup
|
24
25
|
|
25
26
|
module_function
|
26
|
-
def
|
27
|
-
OPTIONS.merge!(DEFAULT_OPTIONS)
|
27
|
+
def restore_defaults!
|
28
|
+
OPTIONS.clear.merge!(DEFAULT_OPTIONS)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -43,7 +44,7 @@ module YAML
|
|
43
44
|
end
|
44
45
|
|
45
46
|
if SafeYAML::YAML_ENGINE == "psych"
|
46
|
-
require "safe_yaml/
|
47
|
+
require "safe_yaml/safe_to_ruby_visitor"
|
47
48
|
require "safe_yaml/psych_resolver"
|
48
49
|
def self.safe_load(yaml, filename=nil)
|
49
50
|
safe_resolver = SafeYAML::PsychResolver.new
|
data/lib/safe_yaml/resolver.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
module SafeYAML
|
2
2
|
class Resolver
|
3
3
|
def initialize
|
4
|
-
@whitelist
|
5
|
-
@initializers
|
4
|
+
@whitelist = SafeYAML::OPTIONS[:whitelisted_tags] || []
|
5
|
+
@initializers = SafeYAML::OPTIONS[:custom_initializers] || {}
|
6
|
+
@raise_on_unknown_tag = SafeYAML::OPTIONS[:raise_on_unknown_tag]
|
6
7
|
end
|
7
8
|
|
8
9
|
def resolve_node(node)
|
@@ -23,7 +24,7 @@ module SafeYAML
|
|
23
24
|
end
|
24
25
|
|
25
26
|
def resolve_map(node)
|
26
|
-
tag =
|
27
|
+
tag = get_and_check_node_tag(node)
|
27
28
|
return self.native_resolve(node) if tag_is_whitelisted?(tag)
|
28
29
|
|
29
30
|
hash = @initializers.include?(tag) ? @initializers[tag].call : {}
|
@@ -47,14 +48,22 @@ module SafeYAML
|
|
47
48
|
def resolve_seq(node)
|
48
49
|
seq = self.get_node_value(node)
|
49
50
|
|
50
|
-
tag =
|
51
|
+
tag = get_and_check_node_tag(node)
|
51
52
|
arr = @initializers.include?(tag) ? @initializers[tag].call : []
|
52
53
|
|
53
54
|
seq.inject(arr) { |array, node| array << resolve_node(node) }
|
54
55
|
end
|
55
56
|
|
56
57
|
def resolve_scalar(node)
|
57
|
-
Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node),
|
58
|
+
Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), get_and_check_node_tag(node))
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_and_check_node_tag(node)
|
62
|
+
tag = self.get_node_tag(node)
|
63
|
+
if !!tag && @raise_on_unknown_tag && !tag_is_whitelisted?(tag)
|
64
|
+
raise "Unknown YAML tag '#{tag}'"
|
65
|
+
end
|
66
|
+
tag
|
58
67
|
end
|
59
68
|
|
60
69
|
def tag_is_whitelisted?(tag)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SafeYAML
|
2
|
+
class SafeToRubyVisitor < Psych::Visitors::ToRuby
|
3
|
+
def initialize(resolver)
|
4
|
+
super()
|
5
|
+
@resolver = resolver
|
6
|
+
end
|
7
|
+
|
8
|
+
def accept(node)
|
9
|
+
if node.tag
|
10
|
+
return super if @resolver.tag_is_whitelisted?(node.tag)
|
11
|
+
raise "Unknown YAML tag '#{node.tag}'" if SafeYAML::OPTIONS[:raise_on_unknown_tag]
|
12
|
+
end
|
13
|
+
|
14
|
+
@resolver.resolve_node(node)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
monkeypatch = <<-EORUBY
|
2
2
|
class Node
|
3
3
|
def safe_transform
|
4
|
-
|
4
|
+
if self.type_id
|
5
|
+
return unsafe_transform if SafeYAML::OPTIONS[:whitelisted_tags].include?(self.type_id)
|
6
|
+
raise "Unknown YAML tag '#{self.type_id}'" if SafeYAML::OPTIONS[:raise_on_unknown_tag]
|
7
|
+
end
|
8
|
+
|
5
9
|
SafeYAML::SyckResolver.new.resolve_node(self)
|
6
10
|
end
|
7
11
|
|
data/lib/safe_yaml/version.rb
CHANGED
data/spec/resolver_specs.rb
CHANGED
data/spec/safe_yaml_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe YAML do
|
|
43
43
|
end
|
44
44
|
|
45
45
|
after :each do
|
46
|
-
SafeYAML.
|
46
|
+
SafeYAML.restore_defaults!
|
47
47
|
end
|
48
48
|
|
49
49
|
it "effectively ignores the whitelist (since everything is whitelisted)" do
|
@@ -256,7 +256,7 @@ describe YAML do
|
|
256
256
|
end
|
257
257
|
|
258
258
|
after :each do
|
259
|
-
SafeYAML.
|
259
|
+
SafeYAML.restore_defaults!
|
260
260
|
end
|
261
261
|
|
262
262
|
it "will use a custom initializer to instantiate an array-like class upon deserialization" do
|
@@ -289,10 +289,13 @@ describe YAML do
|
|
289
289
|
else
|
290
290
|
SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
|
291
291
|
end
|
292
|
+
|
293
|
+
# Necessary for deserializing OpenStructs properly.
|
294
|
+
SafeYAML::OPTIONS[:deserialize_symbols] = true
|
292
295
|
end
|
293
296
|
|
294
297
|
after :each do
|
295
|
-
SafeYAML.
|
298
|
+
SafeYAML.restore_defaults!
|
296
299
|
end
|
297
300
|
|
298
301
|
it "will allow objects to be deserialized for whitelisted tags" do
|
@@ -317,7 +320,49 @@ describe YAML do
|
|
317
320
|
|
318
321
|
result.should be_a(OpenStruct)
|
319
322
|
result.backdoor.should_not be_a(ExploitableBackDoor)
|
320
|
-
result.
|
323
|
+
result.backdoor.should == { "foo" => "bar" }
|
324
|
+
end
|
325
|
+
|
326
|
+
context "with the :raise_on_unknown_tag option enabled" do
|
327
|
+
before :each do
|
328
|
+
SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
|
329
|
+
end
|
330
|
+
|
331
|
+
after :each do
|
332
|
+
SafeYAML.restore_defaults!
|
333
|
+
end
|
334
|
+
|
335
|
+
it "raises an exception if a non-nil, non-whitelisted tag is encountered" do
|
336
|
+
lambda {
|
337
|
+
YAML.safe_load <<-YAML.unindent
|
338
|
+
--- !ruby/object:Unknown
|
339
|
+
foo: bar
|
340
|
+
YAML
|
341
|
+
}.should raise_error
|
342
|
+
end
|
343
|
+
|
344
|
+
it "checks all tags, even those within objects with trusted tags" do
|
345
|
+
lambda {
|
346
|
+
YAML.safe_load <<-YAML.unindent
|
347
|
+
--- !ruby/object:OpenStruct
|
348
|
+
table:
|
349
|
+
:backdoor: !ruby/object:Unknown
|
350
|
+
foo: bar
|
351
|
+
YAML
|
352
|
+
}.should raise_error
|
353
|
+
end
|
354
|
+
|
355
|
+
it "does not raise an exception as long as all tags are whitelisted" do
|
356
|
+
result = YAML.safe_load <<-YAML.unindent
|
357
|
+
--- !ruby/object:OpenStruct
|
358
|
+
table:
|
359
|
+
:backdoor:
|
360
|
+
foo: bar
|
361
|
+
YAML
|
362
|
+
|
363
|
+
result.should be_a(OpenStruct)
|
364
|
+
result.backdoor.should == { "foo" => "bar" }
|
365
|
+
end
|
321
366
|
end
|
322
367
|
end
|
323
368
|
end
|
@@ -366,7 +411,7 @@ describe YAML do
|
|
366
411
|
|
367
412
|
context "as long as a :default_mode has been specified" do
|
368
413
|
after :each do
|
369
|
-
SafeYAML.
|
414
|
+
SafeYAML.restore_defaults!
|
370
415
|
end
|
371
416
|
|
372
417
|
it "doesn't issue a warning for safe mode, since an explicit mode has been set" do
|
@@ -417,7 +462,7 @@ describe YAML do
|
|
417
462
|
end
|
418
463
|
|
419
464
|
after :each do
|
420
|
-
SafeYAML.
|
465
|
+
SafeYAML.restore_defaults!
|
421
466
|
end
|
422
467
|
|
423
468
|
it "defaults to unsafe mode if the :safe option is omitted" do
|
@@ -472,7 +517,7 @@ describe YAML do
|
|
472
517
|
end
|
473
518
|
|
474
519
|
after :each do
|
475
|
-
SafeYAML.
|
520
|
+
SafeYAML.restore_defaults!
|
476
521
|
end
|
477
522
|
|
478
523
|
it "defaults to unsafe mode if the :safe option is omitted" do
|
metadata
CHANGED
@@ -1,32 +1,23 @@
|
|
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
|
+
version: 0.8.1
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 8
|
9
|
-
- 0
|
10
|
-
version: 0.8.0
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Dan Tao
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
date: 2013-02-17 00:00:00 Z
|
12
|
+
date: 2013-02-17 00:00:00.000000000 Z
|
19
13
|
dependencies: []
|
20
|
-
|
21
|
-
|
14
|
+
description: Parse YAML safely, without that pesky arbitrary object deserialization
|
15
|
+
vulnerability
|
22
16
|
email: daniel.tao@gmail.com
|
23
17
|
executables: []
|
24
|
-
|
25
18
|
extensions: []
|
26
|
-
|
27
19
|
extra_rdoc_files: []
|
28
|
-
|
29
|
-
files:
|
20
|
+
files:
|
30
21
|
- .gitignore
|
31
22
|
- .travis.yml
|
32
23
|
- Gemfile
|
@@ -35,8 +26,8 @@ files:
|
|
35
26
|
- Rakefile
|
36
27
|
- lib/safe_yaml.rb
|
37
28
|
- lib/safe_yaml/psych_resolver.rb
|
38
|
-
- lib/safe_yaml/psych_visitor.rb
|
39
29
|
- lib/safe_yaml/resolver.rb
|
30
|
+
- lib/safe_yaml/safe_to_ruby_visitor.rb
|
40
31
|
- lib/safe_yaml/syck_node_monkeypatch.rb
|
41
32
|
- lib/safe_yaml/syck_resolver.rb
|
42
33
|
- lib/safe_yaml/transform.rb
|
@@ -64,41 +55,32 @@ files:
|
|
64
55
|
- spec/transform/to_symbol_spec.rb
|
65
56
|
- spec/transform/to_time_spec.rb
|
66
57
|
homepage: http://dtao.github.com/safe_yaml/
|
67
|
-
licenses:
|
58
|
+
licenses:
|
68
59
|
- MIT
|
69
60
|
post_install_message:
|
70
61
|
rdoc_options: []
|
71
|
-
|
72
|
-
require_paths:
|
62
|
+
require_paths:
|
73
63
|
- lib
|
74
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
65
|
none: false
|
76
|
-
requirements:
|
77
|
-
- -
|
78
|
-
- !ruby/object:Gem::Version
|
79
|
-
hash: 57
|
80
|
-
segments:
|
81
|
-
- 1
|
82
|
-
- 8
|
83
|
-
- 7
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
84
69
|
version: 1.8.7
|
85
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
71
|
none: false
|
87
|
-
requirements:
|
88
|
-
- -
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
|
91
|
-
segments:
|
92
|
-
- 0
|
93
|
-
version: "0"
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
94
76
|
requirements: []
|
95
|
-
|
96
77
|
rubyforge_project:
|
97
78
|
rubygems_version: 1.8.25
|
98
79
|
signing_key:
|
99
80
|
specification_version: 3
|
100
|
-
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
101
|
-
|
81
|
+
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
82
|
+
accepting user input in Ruby applications.
|
83
|
+
test_files:
|
102
84
|
- spec/exploit.1.9.2.yaml
|
103
85
|
- spec/exploit.1.9.3.yaml
|
104
86
|
- spec/psych_resolver_spec.rb
|
@@ -1,13 +0,0 @@
|
|
1
|
-
module SafeYAML
|
2
|
-
class PsychVisitor < Psych::Visitors::ToRuby
|
3
|
-
def initialize(resolver)
|
4
|
-
super()
|
5
|
-
@resolver = resolver
|
6
|
-
end
|
7
|
-
|
8
|
-
def accept(node)
|
9
|
-
return super if @resolver.tag_is_whitelisted?(@resolver.get_node_tag(node))
|
10
|
-
@resolver.resolve_node(node)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|