safe_yaml 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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