safe_yaml 0.5.2 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,25 @@
1
+ language: ruby
2
+ before_install:
3
+ - gem install bundler
4
+ script: "bundle exec rake spec"
5
+ rvm:
6
+ - ruby-head
7
+ - 2.0.0
8
+ - 1.9.3
9
+ - 1.9.2
10
+ - 1.8.7
11
+ - rbx-19mode
12
+ - rbx-18mode
13
+ - jruby-head
14
+ - jruby-19mode
15
+ - jruby-18mode
16
+ - ree
17
+ matrix:
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: rbx-19mode
21
+ - rvm: rbx-18mode
22
+ - rvm: jruby-head
23
+ - rvm: jruby-19mode
24
+ - rvm: jruby-18mode
25
+ - rvm: ree
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Dan Tao
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  SafeYAML
2
2
  ========
3
3
 
4
- The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the one recently discovered in Rails](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/)).
4
+ The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the one recently discovered in Rails](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/) (or [this one](http://www.h-online.com/open/news/item/Rails-developers-close-another-extremely-critical-flaw-1793511.html))).
5
5
 
6
6
  Installation
7
7
  ------------
@@ -18,8 +18,8 @@ Or install it yourself as:
18
18
 
19
19
  $ gem install safe_yaml
20
20
 
21
- Usage
22
- -----
21
+ Purpose
22
+ -------
23
23
 
24
24
  Suppose your application were to contain some code like this:
25
25
 
@@ -62,6 +62,25 @@ With SafeYAML, that attacker would be thwarted:
62
62
  > YAML.load(yaml)
63
63
  => {"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}
64
64
 
65
+ Usage
66
+ -----
67
+
68
+ `YAML.safe_load` will load YAML without allowing arbitrary object deserialization.
69
+
70
+ `YAML.unsafe_load` will exhibit Ruby's built-in behavior: to allow the deserialization of arbitrary objects.
71
+
72
+ By default, when you require the safe_yaml gem in your project, `YAML.load` is patched to internally call `safe_load`. The patched method also accepts a `:safe` flag to specify which version to use:
73
+
74
+ # Ruby >= 1.9.3
75
+ YAML.load(yaml, filename, :safe => true) # calls safe_load
76
+ YAML.load(yaml, filename, :safe => false) # calls unsafe_load
77
+
78
+ # Ruby < 1.9.3
79
+ YAML.load(yaml, :safe => true) # calls safe_load
80
+ YAML.load(yaml, :safe => false) # calls unsafe_load
81
+
82
+ The default behavior can be switched to unsafe loading by calling `YAML.enable_arbitrary_object_deserialization!`. In this case, the `:safe` flag still has the same effect, but the defaults are reversed (so calling `YAML.load` will have the same behavior as if the safe_yaml gem weren't required).
83
+
65
84
  Notes
66
85
  -----
67
86
 
@@ -76,11 +95,19 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
76
95
  - Booleans
77
96
  - Nils
78
97
 
79
- Additionally, symbols will also be deserialized if the `YAML.enable_symbol_parsing` option is set to `true`.
98
+ Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!`.
80
99
 
81
- For scenarios where the data to be parsed is from a trusted source and it is still desirable to deserialize arbitrary Ruby objects, the original implementation of `YAML.load` is available as `YAML.orig_load`.
100
+ Caveat
101
+ ------
102
+
103
+ Obviously this gem is quite young, and so the API may (read: will) change in future versions. The goal of the gem is to make it as easy as possible to protect existing applications from object deserialization exploits. Any and all feedback is more than welcome.
82
104
 
83
105
  Requirements
84
106
  ------------
85
107
 
86
108
  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).
109
+
110
+ Code Status
111
+ -------------
112
+
113
+ [![Build Status](https://secure.travis-ci.org/dtao/safe_yaml.png)](http://travis-ci.org/dtao/safe_yaml)
@@ -5,14 +5,16 @@ module SafeYAML
5
5
  def initialize
6
6
  @anchors = {}
7
7
  @stack = []
8
+ @current_key = nil
9
+ @result = nil
8
10
  end
9
11
 
10
12
  def result
11
13
  @result
12
14
  end
13
15
 
14
- def add_to_current_structure(value, anchor=nil)
15
- value = Transform.to_proper_type(value)
16
+ def add_to_current_structure(value, anchor=nil, quoted=nil)
17
+ value = Transform.to_proper_type(value, quoted)
16
18
 
17
19
  @anchors[anchor] = value if anchor
18
20
 
@@ -60,7 +62,7 @@ module SafeYAML
60
62
  end
61
63
 
62
64
  def scalar(value, anchor, tag, plain, quoted, style)
63
- add_to_current_structure(value, anchor)
65
+ add_to_current_structure(value, anchor, quoted)
64
66
  end
65
67
 
66
68
  def start_mapping(anchor, tag, implicit, style)
@@ -1,19 +1,23 @@
1
1
  module SafeYAML
2
2
  class SyckResolver
3
+ QUOTE_STYLES = [:quote1, :quote2]
4
+
3
5
  def resolve_node(node)
4
6
  case node.kind
5
7
  when :map
6
- return resolve_map(node.value)
8
+ return resolve_map(node)
7
9
  when :seq
8
- return resolve_seq(node.value)
10
+ return resolve_seq(node)
9
11
  when :scalar
10
- return resolve_scalar(node.value)
12
+ return resolve_scalar(node)
11
13
  else
12
14
  raise "Don't know how to resolve a '#{node.kind}' node!"
13
15
  end
14
16
  end
15
17
 
16
- def resolve_map(map)
18
+ def resolve_map(node)
19
+ map = node.value
20
+
17
21
  hash = {}
18
22
  map.each do |key_node, value_node|
19
23
  if resolve_node(key_node) == "<<"
@@ -22,15 +26,18 @@ module SafeYAML
22
26
  hash[resolve_node(key_node)] = resolve_node(value_node)
23
27
  end
24
28
  end
29
+
25
30
  return hash
26
31
  end
27
32
 
28
- def resolve_seq(seq)
33
+ def resolve_seq(node)
34
+ seq = node.value
35
+
29
36
  seq.map { |node| resolve_node(node) }
30
37
  end
31
38
 
32
- def resolve_scalar(scalar)
33
- Transform.to_proper_type(scalar)
39
+ def resolve_scalar(node)
40
+ Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)))
34
41
  end
35
42
  end
36
43
  end
@@ -1,7 +1,7 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToDate
4
- MATCHER = /^\d{4}\-\d{2}\-\d{2}$/.freeze
4
+ MATCHER = /\A\d{4}\-\d{2}\-\d{2}\Z/.freeze
5
5
 
6
6
  def transform?(value)
7
7
  return false unless MATCHER.match(value)
@@ -1,7 +1,7 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToFloat
4
- MATCHER = /^(?:\d+(?:\.\d*)?$)|(?:^\.\d+$)/.freeze
4
+ MATCHER = /\A(?:\d+(?:\.\d*)?\Z)|(?:^\.\d+\Z)/.freeze
5
5
 
6
6
  def transform?(value)
7
7
  return false unless MATCHER.match(value)
@@ -1,7 +1,7 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToInteger
4
- MATCHER = /^\d+$/.freeze
4
+ MATCHER = /\A\d+\Z/.freeze
5
5
 
6
6
  def transform?(value)
7
7
  return false unless MATCHER.match(value)
@@ -1,10 +1,10 @@
1
1
  module SafeYAML
2
2
  class Transform
3
3
  class ToSymbol
4
- MATCHER = /^:\w+$/.freeze
4
+ MATCHER = /\A:\w+\Z/.freeze
5
5
 
6
6
  def transform?(value)
7
- return false if !Transform::OPTIONS[:enable_symbol_parsing] || !MATCHER.match(value)
7
+ return false unless SafeYAML::OPTIONS[:enable_symbol_parsing] && MATCHER.match(value)
8
8
  return true, value[1..-1].to_sym
9
9
  end
10
10
  end
@@ -3,7 +3,7 @@ module SafeYAML
3
3
  class ToTime
4
4
  # There isn't a missing '$' there; YAML itself seems to ignore everything at the end of a
5
5
  # string that otherwise resembles a time.
6
- MATCHER = /^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,5})?/.freeze
6
+ MATCHER = /\A\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,5})?/.freeze
7
7
 
8
8
  def transform?(value)
9
9
  return false unless MATCHER.match(value)
@@ -1,9 +1,5 @@
1
1
  module SafeYAML
2
2
  class Transform
3
- OPTIONS = {
4
- :enable_symbol_parsing => false
5
- }
6
-
7
3
  TRANSFORMERS = [
8
4
  Transform::ToSymbol.new,
9
5
  Transform::ToInteger.new,
@@ -14,7 +10,9 @@ module SafeYAML
14
10
  Transform::ToTime.new
15
11
  ]
16
12
 
17
- def self.to_proper_type(value)
13
+ def self.to_proper_type(value, quoted=false)
14
+ return value if quoted
15
+
18
16
  if value.is_a?(String)
19
17
  TRANSFORMERS.each do |transformer|
20
18
  success, transformed_value = transformer.transform?(value)
@@ -1,3 +1,3 @@
1
1
  module SafeYAML
2
- VERSION = "0.5.2"
2
+ VERSION = "0.6.1"
3
3
  end
data/lib/safe_yaml.rb CHANGED
@@ -9,7 +9,34 @@ require "safe_yaml/transform/to_time"
9
9
  require "safe_yaml/transform"
10
10
  require "safe_yaml/version"
11
11
 
12
+ module SafeYAML
13
+ OPTIONS = {
14
+ :enable_symbol_parsing => false,
15
+ :enable_arbitrary_object_deserialization => false
16
+ }
17
+ end
18
+
12
19
  module YAML
20
+ def self.load_with_options(yaml, options={})
21
+ safe_mode = safe_mode_from_options("load", options)
22
+
23
+ arguments = [yaml]
24
+ if RUBY_VERSION >= "1.9.3"
25
+ arguments << options[:filename]
26
+ end
27
+
28
+ safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
29
+ end
30
+
31
+ def self.load_file_with_options(file, options={})
32
+ safe_mode = safe_mode_from_options("load_file", options)
33
+ safe_mode ? safe_load_file(file) : unsafe_load_file(file)
34
+ end
35
+
36
+ def self.load_with_filename_and_options(yaml, filename=nil, options={})
37
+ load_with_options(yaml, options.merge(:filename => filename))
38
+ end
39
+
13
40
  if RUBY_VERSION >= "1.9.3"
14
41
  require "safe_yaml/psych_handler"
15
42
  def self.safe_load(yaml, filename=nil)
@@ -18,9 +45,13 @@ module YAML
18
45
  return safe_handler.result
19
46
  end
20
47
 
21
- def self.orig_load_file(filename)
48
+ def self.safe_load_file(filename)
49
+ File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f, filename }
50
+ end
51
+
52
+ def self.unsafe_load_file(filename)
22
53
  # https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
23
- File.open(filename, 'r:bom|utf-8') { |f| self.orig_load f, filename }
54
+ File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
24
55
  end
25
56
 
26
57
  elsif RUBY_VERSION == "1.9.2"
@@ -31,9 +62,13 @@ module YAML
31
62
  return safe_handler.result
32
63
  end
33
64
 
34
- def self.orig_load_file(filename)
65
+ def self.safe_load_file(filename)
66
+ File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f }
67
+ end
68
+
69
+ def self.unsafe_load_file(filename)
35
70
  # https://github.com/tenderlove/psych/blob/v1.2.0/lib/psych.rb#L228-230
36
- File.open(filename, 'r:bom|utf-8') { |f| self.orig_load f }
71
+ File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f }
37
72
  end
38
73
 
39
74
  else
@@ -44,22 +79,62 @@ module YAML
44
79
  return safe_resolver.resolve_node(tree)
45
80
  end
46
81
 
47
- def self.orig_load_file(filename)
82
+ def self.safe_load_file(filename)
83
+ File.open(filename) { |f| self.safe_load f }
84
+ end
85
+
86
+ def self.unsafe_load_file(filename)
48
87
  # https://github.com/indeyets/syck/blob/master/ext/ruby/lib/yaml.rb#L133-135
49
- File.open(filename) { |f| self.orig_load f }
88
+ File.open(filename) { |f| self.unsafe_load f }
50
89
  end
51
90
  end
52
91
 
53
92
  class << self
54
- alias_method :orig_load, :load
55
- alias_method :load, :safe_load
93
+ alias_method :unsafe_load, :load
56
94
 
57
- def enable_symbol_parsing
58
- SafeYAML::Transform::OPTIONS[:enable_symbol_parsing]
95
+ if RUBY_VERSION >= "1.9.3"
96
+ alias_method :load, :load_with_filename_and_options
97
+ else
98
+ alias_method :load, :load_with_options
59
99
  end
60
100
 
61
- def enable_symbol_parsing=(value)
62
- SafeYAML::Transform::OPTIONS[:enable_symbol_parsing] = value
101
+ alias_method :load_file, :load_file_with_options
102
+
103
+ def enable_symbol_parsing?
104
+ SafeYAML::OPTIONS[:enable_symbol_parsing]
105
+ end
106
+
107
+ def enable_symbol_parsing!
108
+ SafeYAML::OPTIONS[:enable_symbol_parsing] = true
109
+ end
110
+
111
+ def disable_symbol_parsing!
112
+ SafeYAML::OPTIONS[:enable_symbol_parsing] = false
113
+ end
114
+
115
+ def enable_arbitrary_object_deserialization?
116
+ SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
117
+ end
118
+
119
+ def enable_arbitrary_object_deserialization!
120
+ SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = true
121
+ end
122
+
123
+ def disable_arbitrary_object_deserialization!
124
+ SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = false
125
+ end
126
+ end
127
+
128
+ private
129
+ def self.safe_mode_from_options(method, options={})
130
+ safe_mode = options[:safe]
131
+
132
+ if safe_mode.nil?
133
+ mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
134
+ Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode."
135
+ safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
63
136
  end
137
+
138
+ safe_mode
64
139
  end
65
140
  end
@@ -10,3 +10,6 @@ rake spec
10
10
 
11
11
  rvm use 1.9.3@safe_yaml
12
12
  rake spec
13
+
14
+ rvm use 2.0.0@safe_yaml
15
+ rake spec
data/safe_yaml.gemspec CHANGED
@@ -6,10 +6,10 @@ Gem::Specification.new do |gem|
6
6
  gem.version = SafeYAML::VERSION
7
7
  gem.authors = "Dan Tao"
8
8
  gem.email = "daniel.tao@gmail.com"
9
- gem.description = %q{Parse YAML safely, without that pesky arbitrary code execution vulnerability}
9
+ gem.description = %q{Parse YAML safely, without that pesky arbitrary object deserialization vulnerability}
10
10
  gem.summary = %q{SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.}
11
11
  gem.homepage = "http://dtao.github.com/safe_yaml/"
12
-
12
+ gem.license = "MIT"
13
13
  gem.files = `git ls-files`.split($\)
14
14
  gem.test_files = gem.files.grep(%r{^spec/})
15
15
  gem.require_paths = ["lib"]
@@ -3,42 +3,50 @@ require File.join(File.dirname(__FILE__), "spec_helper")
3
3
  require "exploitable_back_door"
4
4
 
5
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
+
6
14
  before :each do
7
- YAML.enable_symbol_parsing = false
15
+ YAML.disable_symbol_parsing!
8
16
  end
9
17
 
10
- describe "orig_load" do
18
+ describe "unsafe_load" do
11
19
  if RUBY_VERSION >= "1.9.3"
12
20
  it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
13
- backdoor = YAML.orig_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
21
+ backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
14
22
  backdoor.should be_exploited_through_setter
15
23
  end
16
24
 
17
25
  it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
18
- backdoor = YAML.orig_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
26
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
19
27
  backdoor.should be_exploited_through_init_with
20
28
  end
21
29
  end
22
30
 
23
31
  it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
24
- backdoor = YAML.orig_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
32
+ backdoor = YAML.unsafe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
25
33
  backdoor.should be_exploited_through_ivars
26
34
  end
27
35
  end
28
36
 
29
- describe "load" do
37
+ describe "safe_load" do
30
38
  it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
31
- object = YAML.load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
39
+ object = YAML.safe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
32
40
  object.should_not be_a(ExploitableBackDoor)
33
41
  end
34
42
 
35
43
  it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
36
- object = YAML.load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
44
+ object = YAML.safe_load("--- !ruby/object:ExploitableBackDoor\nfoo: bar\n")
37
45
  object.should_not be_a(ExploitableBackDoor)
38
46
  end
39
47
 
40
48
  it "loads a plain ol' YAML document just fine" do
41
- result = YAML.load <<-YAML.unindent
49
+ result = YAML.safe_load <<-YAML.unindent
42
50
  foo:
43
51
  number: 1
44
52
  string: Hello, there!
@@ -59,7 +67,7 @@ describe YAML do
59
67
  end
60
68
 
61
69
  it "works for YAML documents with anchors and aliases" do
62
- result = YAML.load <<-YAML
70
+ result = YAML.safe_load <<-YAML
63
71
  - &id001 {}
64
72
  - *id001
65
73
  - *id001
@@ -69,7 +77,7 @@ describe YAML do
69
77
  end
70
78
 
71
79
  it "works for YAML documents with sections" do
72
- result = YAML.load <<-YAML
80
+ result = YAML.safe_load <<-YAML
73
81
  mysql: &mysql
74
82
  adapter: mysql
75
83
  pool: 30
@@ -102,34 +110,150 @@ describe YAML do
102
110
  end
103
111
  end
104
112
 
105
- describe "orig_load_file" do
113
+ describe "unsafe_load_file" do
106
114
  if RUBY_VERSION >= "1.9.3"
107
115
  it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
108
- backdoor = YAML.orig_load_file "spec/exploit.1.9.3.yaml"
116
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
109
117
  backdoor.should be_exploited_through_setter
110
118
  end
111
119
 
112
120
  it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
113
- backdoor = YAML.orig_load_file "spec/exploit.1.9.2.yaml"
121
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
114
122
  backdoor.should be_exploited_through_init_with
115
123
  end
116
124
  end
117
125
 
118
126
  it "allows exploits through objects w/ sensitive instance variables defined in YAML w/ !ruby/object" do
119
- backdoor = YAML.orig_load_file "spec/exploit.1.9.2.yaml"
127
+ backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
120
128
  backdoor.should be_exploited_through_ivars
121
129
  end
122
130
  end
123
131
 
124
- describe "load_file" do
132
+ describe "safe_load_file" do
125
133
  it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
126
- object = YAML.load_file "spec/exploit.1.9.3.yaml"
134
+ object = YAML.safe_load_file "spec/exploit.1.9.3.yaml"
127
135
  object.should_not be_a(ExploitableBackDoor)
128
136
  end
129
137
 
130
138
  it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
131
- object = YAML.load_file "spec/exploit.1.9.2.yaml"
139
+ object = YAML.safe_load_file "spec/exploit.1.9.2.yaml"
132
140
  object.should_not be_a(ExploitableBackDoor)
133
141
  end
134
142
  end
143
+
144
+ describe "load" do
145
+ let (:arguments) {
146
+ if RUBY_VERSION >= "1.9.3"
147
+ ["foo: bar", nil]
148
+ else
149
+ ["foo: bar"]
150
+ end
151
+ }
152
+
153
+ it "issues a warning if the :safe option is omitted" do
154
+ silence_warnings do
155
+ Kernel.should_receive(:warn)
156
+ YAML.load(*arguments)
157
+ end
158
+ end
159
+
160
+ it "doesn't issue a warning as long as the :safe option is specified" do
161
+ Kernel.should_not_receive(:warn)
162
+ YAML.load(*(arguments + [{:safe => true}]))
163
+ end
164
+
165
+ it "defaults to safe mode if the :safe option is omitted" do
166
+ silence_warnings do
167
+ YAML.should_receive(:safe_load).with(*arguments)
168
+ YAML.load(*arguments)
169
+ end
170
+ end
171
+
172
+ it "calls #safe_load if the :safe option is set to true" do
173
+ YAML.should_receive(:safe_load).with(*arguments)
174
+ YAML.load(*(arguments + [{:safe => true}]))
175
+ end
176
+
177
+ it "calls #unsafe_load if the :safe option is set to false" do
178
+ YAML.should_receive(:unsafe_load).with(*arguments)
179
+ YAML.load(*(arguments + [{:safe => false}]))
180
+ end
181
+
182
+ context "with arbitrary object deserialization enabled by default" do
183
+ before :each do
184
+ YAML.enable_arbitrary_object_deserialization!
185
+ end
186
+
187
+ after :each do
188
+ YAML.disable_arbitrary_object_deserialization!
189
+ end
190
+
191
+ it "defaults to unsafe mode if the :safe option is omitted" do
192
+ silence_warnings do
193
+ YAML.should_receive(:unsafe_load).with(*arguments)
194
+ YAML.load(*arguments)
195
+ end
196
+ end
197
+
198
+ it "calls #safe_load if the :safe option is set to true" do
199
+ YAML.should_receive(:safe_load).with(*arguments)
200
+ YAML.load(*(arguments + [{:safe => true}]))
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "load_file" do
206
+ let(:filename) { "spec/exploit.1.9.2.yaml" } # doesn't really matter
207
+
208
+ it "issues a warning if the :safe option is omitted" do
209
+ silence_warnings do
210
+ Kernel.should_receive(:warn)
211
+ YAML.load_file(filename)
212
+ end
213
+ end
214
+
215
+ it "doesn't issue a warning as long as the :safe option is specified" do
216
+ Kernel.should_not_receive(:warn)
217
+ YAML.load_file(filename, :safe => true)
218
+ end
219
+
220
+ it "defaults to safe mode if the :safe option is omitted" do
221
+ silence_warnings do
222
+ YAML.should_receive(:safe_load_file).with(filename)
223
+ YAML.load_file(filename)
224
+ end
225
+ end
226
+
227
+ it "calls #safe_load_file if the :safe option is set to true" do
228
+ YAML.should_receive(:safe_load_file).with(filename)
229
+ YAML.load_file(filename, :safe => true)
230
+ end
231
+
232
+ it "calls #unsafe_load_file if the :safe option is set to false" do
233
+ YAML.should_receive(:unsafe_load_file).with(filename)
234
+ YAML.load_file(filename, :safe => false)
235
+ end
236
+
237
+ context "with arbitrary object deserialization enabled by default" do
238
+ before :each do
239
+ YAML.enable_arbitrary_object_deserialization!
240
+ end
241
+
242
+ after :each do
243
+ YAML.disable_arbitrary_object_deserialization!
244
+ end
245
+
246
+ it "defaults to unsafe mode if the :safe option is omitted" do
247
+ silence_warnings do
248
+ YAML.should_receive(:unsafe_load_file).with(filename)
249
+ YAML.load_file(filename)
250
+ end
251
+ end
252
+
253
+ it "calls #safe_load if the :safe option is set to true" do
254
+ YAML.should_receive(:safe_load_file).with(filename)
255
+ YAML.load_file(filename, :safe => true)
256
+ end
257
+ end
258
+ end
135
259
  end
data/spec/shared_specs.rb CHANGED
@@ -70,6 +70,11 @@ module SharedSpecs
70
70
  result.should == [nil] * 3
71
71
  end
72
72
 
73
+ it "translates quoted empty strings to strings (not nil)" do
74
+ parse "foo: ''"
75
+ result.should == { "foo" => "" }
76
+ end
77
+
73
78
  it "deals just fine with nested maps" do
74
79
  parse <<-YAML
75
80
  foo:
@@ -132,16 +137,19 @@ module SharedSpecs
132
137
  result.should == { "time" => Time.new(2013, 1, 29, 5, 58, 0, "-08:00") }
133
138
  end
134
139
 
135
- it "applies the same transformation to keys" do
136
- parse "2013-01-29 05:58:00 -0800: time"
137
- result.should == { Time.new(2013, 1, 29, 5, 58, 0, "-08:00") => "time" }
138
- end
139
-
140
140
  it "applies the same transformation to elements in sequences" do
141
141
  parse "- 2013-01-29 05:58:00 -0800"
142
142
  result.should == [Time.new(2013, 1, 29, 5, 58, 0, "-08:00")]
143
143
  end
144
144
 
145
+ # On Ruby 2.0.0-rc1, even YAML.load overflows the stack on this input.
146
+ if RUBY_VERSION != "2.0.0"
147
+ it "applies the same transformation to keys" do
148
+ parse "2013-01-29 05:58:00 -0800: time"
149
+ result.should == { Time.new(2013, 1, 29, 5, 58, 0, "-08:00") => "time" }
150
+ end
151
+ end
152
+
145
153
  else
146
154
  it "does not deserialize times" do
147
155
  parse "time: 2013-01-29 05:58:00 -0800"
@@ -152,7 +160,11 @@ module SharedSpecs
152
160
 
153
161
  context "with symbol parsing enabled" do
154
162
  before :each do
155
- YAML.enable_symbol_parsing = true
163
+ YAML.enable_symbol_parsing!
164
+ end
165
+
166
+ after :each do
167
+ YAML.disable_symbol_parsing!
156
168
  end
157
169
 
158
170
  it "translates values starting with ':' to symbols" do
@@ -162,13 +174,11 @@ module SharedSpecs
162
174
 
163
175
  it "applies the same transformation to keys" do
164
176
  parse ":bar: symbol"
165
-
166
177
  result.should == { :bar => "symbol" }
167
178
  end
168
179
 
169
180
  it "applies the same transformation to elements in sequences" do
170
181
  parse "- :bar"
171
-
172
182
  result.should == [:bar]
173
183
  end
174
184
  end
@@ -0,0 +1,19 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToDate do
4
+ it "returns true when the value matches a valid Date" do
5
+ subject.transform?("2013-01-01")[0].should == true
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Date" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value does not end with a Date" do
13
+ subject.transform?("2013-01-01\nNOT A DATE").should be_false
14
+ end
15
+
16
+ it "returns false when the value does not begin with a Date" do
17
+ subject.transform?("NOT A DATE\n2013-01-01").should be_false
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToFloat do
4
+ it "returns true when the value matches a valid Float" do
5
+ subject.transform?("20.00").should be_true
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Float" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value spans multiple lines" do
13
+ subject.transform?("20.00\nNOT A FLOAT").should be_false
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToInteger do
4
+ it "returns true when the value matches a valid Integer" do
5
+ subject.transform?("10")[0].should be_true
6
+ end
7
+
8
+ it "returns false when the value does not match a valid Integer" do
9
+ subject.transform?("foobar").should be_false
10
+ end
11
+
12
+ it "returns false when the value spans multiple lines" do
13
+ subject.transform?("10\nNOT AN INTEGER").should be_false
14
+ end
15
+ end
@@ -0,0 +1,45 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToSymbol do
4
+ def with_symbol_parsing_value(value)
5
+ symbol_parsing_flag = YAML.enable_symbol_parsing?
6
+ SafeYAML::OPTIONS[:enable_symbol_parsing] = value
7
+
8
+ yield
9
+
10
+ ensure
11
+ SafeYAML::OPTIONS[:enable_symbol_parsing] = symbol_parsing_flag
12
+ end
13
+
14
+ def with_symbol_parsing(&block)
15
+ with_symbol_parsing_value(true, &block)
16
+ end
17
+
18
+ def without_symbol_parsing(&block)
19
+ with_symbol_parsing_value(false, &block)
20
+ end
21
+
22
+ it "returns true when the value matches a valid Symbol" do
23
+ with_symbol_parsing { subject.transform?(":foo")[0].should be_true }
24
+ end
25
+
26
+ it "returns false when symbol parsing is disabled" do
27
+ without_symbol_parsing { subject.transform?(":foo").should be_false }
28
+ end
29
+
30
+ it "returns false when the value does not match a valid Symbol" do
31
+ with_symbol_parsing { subject.transform?("foo").should be_false }
32
+ end
33
+
34
+ it "returns false when the symbol does not begin the line" do
35
+ with_symbol_parsing do
36
+ subject.transform?("NOT A SYMBOL\n:foo").should be_false
37
+ end
38
+ end
39
+
40
+ it "returns false when the symbol does not end the line" do
41
+ with_symbol_parsing do
42
+ subject.transform?(":foo\nNOT A SYMBOL").should be_false
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ describe SafeYAML::Transform::ToTime do
4
+ # YAML don't parse times prior to 1.9.
5
+ if RUBY_VERSION >= "1.9.2"
6
+ it "returns true when the value matches a valid Time" do
7
+ subject.transform?("2013-01-01 10:00:00")[0].should == true
8
+ end
9
+ end
10
+
11
+ it "returns false when the value does not match a valid Time" do
12
+ subject.transform?("not a time").should be_false
13
+ end
14
+
15
+ it "returns false when the beginning of a line does not match a Time" do
16
+ subject.transform?("NOT A TIME\n2013-01-01 10:00:00").should be_false
17
+ end
18
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: safe_yaml
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,19 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-29 00:00:00.000000000 Z
12
+ date: 2013-02-02 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: Parse YAML safely, without that pesky arbitrary code execution vulnerability
14
+ description: Parse YAML safely, without that pesky arbitrary object deserialization
15
+ vulnerability
15
16
  email: daniel.tao@gmail.com
16
17
  executables: []
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
21
  - .gitignore
22
+ - .travis.yml
21
23
  - Gemfile
24
+ - LICENSE.txt
22
25
  - README.md
23
26
  - Rakefile
24
27
  - lib/safe_yaml.rb
@@ -43,8 +46,14 @@ files:
43
46
  - spec/spec_helper.rb
44
47
  - spec/support/exploitable_back_door.rb
45
48
  - spec/syck_resolver_spec.rb
49
+ - spec/transform/to_date_spec.rb
50
+ - spec/transform/to_float_spec.rb
51
+ - spec/transform/to_integer_spec.rb
52
+ - spec/transform/to_symbol_spec.rb
53
+ - spec/transform/to_time_spec.rb
46
54
  homepage: http://dtao.github.com/safe_yaml/
47
- licenses: []
55
+ licenses:
56
+ - MIT
48
57
  post_install_message:
49
58
  rdoc_options: []
50
59
  require_paths:
@@ -63,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
72
  version: '0'
64
73
  requirements: []
65
74
  rubyforge_project:
66
- rubygems_version: 1.8.24
75
+ rubygems_version: 1.8.25
67
76
  signing_key:
68
77
  specification_version: 3
69
78
  summary: SameYAML provides an alternative implementation of YAML.load suitable for
@@ -77,3 +86,8 @@ test_files:
77
86
  - spec/spec_helper.rb
78
87
  - spec/support/exploitable_back_door.rb
79
88
  - spec/syck_resolver_spec.rb
89
+ - spec/transform/to_date_spec.rb
90
+ - spec/transform/to_float_spec.rb
91
+ - spec/transform/to_integer_spec.rb
92
+ - spec/transform/to_symbol_spec.rb
93
+ - spec/transform/to_time_spec.rb