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 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
- :custom_initializers => {},
18
- :default_mode => nil,
19
- :deserialize_symbols => false,
20
- :whitelisted_tags => []
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 reset_defaults!
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/psych_visitor"
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
@@ -22,7 +22,7 @@ module SafeYAML
22
22
  end
23
23
 
24
24
  def native_resolve(node)
25
- @visitor ||= SafeYAML::PsychVisitor.new(self)
25
+ @visitor ||= SafeYAML::SafeToRubyVisitor.new(self)
26
26
  @visitor.accept(node)
27
27
  end
28
28
 
@@ -1,8 +1,9 @@
1
1
  module SafeYAML
2
2
  class Resolver
3
3
  def initialize
4
- @whitelist = SafeYAML::OPTIONS[:whitelisted_tags] || []
5
- @initializers = SafeYAML::OPTIONS[:custom_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 = self.get_node_tag(node)
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 = get_node_tag(node)
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), self.get_node_tag(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
- return unsafe_transform if SafeYAML::OPTIONS[:whitelisted_tags].include?(self.type_id)
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
 
@@ -1,3 +1,3 @@
1
1
  module SafeYAML
2
- VERSION = "0.8.0"
2
+ VERSION = "0.8.1"
3
3
  end
@@ -204,7 +204,7 @@ module ResolverSpecs
204
204
  end
205
205
 
206
206
  after :each do
207
- SafeYAML.reset_defaults!
207
+ SafeYAML.restore_defaults!
208
208
  end
209
209
 
210
210
  it "translates values starting with ':' to symbols" do
@@ -43,7 +43,7 @@ describe YAML do
43
43
  end
44
44
 
45
45
  after :each do
46
- SafeYAML.reset_defaults!
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.reset_defaults!
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.reset_defaults!
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.instance_variable_get(:@table).should == { ":backdoor" => { "foo" => "bar" } }
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.reset_defaults!
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.reset_defaults!
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.reset_defaults!
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
- hash: 63
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
- description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
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
- hash: 3
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 accepting user input in Ruby applications.
101
- test_files:
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