safe_yaml 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +5 -2
- data/lib/safe_yaml.rb +31 -48
- data/lib/safe_yaml/psych_handler.rb +4 -3
- data/lib/safe_yaml/syck_resolver.rb +1 -1
- data/lib/safe_yaml/transform.rb +14 -1
- data/lib/safe_yaml/version.rb +1 -1
- data/run_specs_all_ruby_versions.sh +9 -3
- data/spec/psych_handler_spec.rb +1 -1
- data/spec/safe_yaml_spec.rb +56 -3
- data/spec/shared_specs.rb +1 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/syck_resolver_spec.rb +1 -1
- data/spec/transform/to_time_spec.rb +1 -1
- metadata +41 -23
data/README.md
CHANGED
@@ -99,14 +99,15 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d
|
|
99
99
|
- Booleans
|
100
100
|
- Nils
|
101
101
|
|
102
|
-
Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing
|
102
|
+
Additionally, deserialization of symbols can be enabled by calling `YAML.enable_symbol_parsing!` (for example, in an initializer).
|
103
103
|
|
104
104
|
Known Issues
|
105
105
|
------------
|
106
106
|
|
107
107
|
Also note that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
|
108
108
|
|
109
|
-
- **Guard
|
109
|
+
- [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so calling `YAML.enable_symbol_parsing!` is necessary to allow Guard to work.
|
110
|
+
- [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so calling `YAML.enable_symbol_parsing!` should allow it to work.
|
110
111
|
|
111
112
|
The above list will grow over time, as more issues are discovered.
|
112
113
|
|
@@ -119,3 +120,5 @@ Requirements
|
|
119
120
|
------------
|
120
121
|
|
121
122
|
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).
|
123
|
+
|
124
|
+
If you are using a version of Ruby where Psych is the default YAML engine (e.g., 1.9.3) but you want to use Syck, be sure to set `YAML::ENGINE.yamler = "syck"` **before** requiring the safe_yaml gem.
|
data/lib/safe_yaml.rb
CHANGED
@@ -10,6 +10,9 @@ require "safe_yaml/transform"
|
|
10
10
|
require "safe_yaml/version"
|
11
11
|
|
12
12
|
module SafeYAML
|
13
|
+
MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
|
14
|
+
YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
|
15
|
+
|
13
16
|
OPTIONS = {
|
14
17
|
:enable_symbol_parsing => false,
|
15
18
|
:enable_arbitrary_object_deserialization => false,
|
@@ -18,14 +21,11 @@ module SafeYAML
|
|
18
21
|
end
|
19
22
|
|
20
23
|
module YAML
|
21
|
-
def self.load_with_options(yaml,
|
24
|
+
def self.load_with_options(yaml, *filename_and_options)
|
25
|
+
options = filename_and_options.last || {}
|
22
26
|
safe_mode = safe_mode_from_options("load", options)
|
23
|
-
|
24
27
|
arguments = [yaml]
|
25
|
-
|
26
|
-
arguments << options[:filename]
|
27
|
-
end
|
28
|
-
|
28
|
+
arguments << filename_and_options.first if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
29
29
|
safe_mode ? safe_load(*arguments) : unsafe_load(*arguments)
|
30
30
|
end
|
31
31
|
|
@@ -34,15 +34,16 @@ module YAML
|
|
34
34
|
safe_mode ? safe_load_file(file) : unsafe_load_file(file)
|
35
35
|
end
|
36
36
|
|
37
|
-
|
38
|
-
load_with_options(yaml, options.merge(:filename => filename))
|
39
|
-
end
|
40
|
-
|
41
|
-
if RUBY_VERSION >= "1.9.3"
|
37
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
42
38
|
require "safe_yaml/psych_handler"
|
43
39
|
def self.safe_load(yaml, filename=nil)
|
44
40
|
safe_handler = SafeYAML::PsychHandler.new
|
45
|
-
Psych::Parser.new(safe_handler)
|
41
|
+
parser = Psych::Parser.new(safe_handler)
|
42
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
43
|
+
parser.parse(yaml, filename)
|
44
|
+
else
|
45
|
+
parser.parse(yaml)
|
46
|
+
end
|
46
47
|
return safe_handler.result
|
47
48
|
end
|
48
49
|
|
@@ -51,25 +52,13 @@ module YAML
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def self.unsafe_load_file(filename)
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
safe_handler = SafeYAML::PsychHandler.new
|
62
|
-
Psych::Parser.new(safe_handler).parse(yaml)
|
63
|
-
return safe_handler.result
|
64
|
-
end
|
65
|
-
|
66
|
-
def self.safe_load_file(filename)
|
67
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.safe_load f }
|
68
|
-
end
|
69
|
-
|
70
|
-
def self.unsafe_load_file(filename)
|
71
|
-
# https://github.com/tenderlove/psych/blob/v1.2.0/lib/psych.rb#L228-230
|
72
|
-
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f }
|
55
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
56
|
+
# https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
|
57
|
+
File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load f, filename }
|
58
|
+
else
|
59
|
+
# https://github.com/tenderlove/psych/blob/v1.2.2/lib/psych.rb#L231-233
|
60
|
+
self.unsafe_load File.open(filename)
|
61
|
+
end
|
73
62
|
end
|
74
63
|
|
75
64
|
else
|
@@ -92,13 +81,7 @@ module YAML
|
|
92
81
|
|
93
82
|
class << self
|
94
83
|
alias_method :unsafe_load, :load
|
95
|
-
|
96
|
-
if RUBY_VERSION >= "1.9.3"
|
97
|
-
alias_method :load, :load_with_filename_and_options
|
98
|
-
else
|
99
|
-
alias_method :load, :load_with_options
|
100
|
-
end
|
101
|
-
|
84
|
+
alias_method :load, :load_with_options
|
102
85
|
alias_method :load_file, :load_file_with_options
|
103
86
|
|
104
87
|
def enable_symbol_parsing?
|
@@ -124,18 +107,18 @@ module YAML
|
|
124
107
|
def disable_arbitrary_object_deserialization!
|
125
108
|
SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] = false
|
126
109
|
end
|
127
|
-
end
|
128
110
|
|
129
|
-
|
130
|
-
|
131
|
-
|
111
|
+
private
|
112
|
+
def safe_mode_from_options(method, options={})
|
113
|
+
safe_mode = options[:safe]
|
132
114
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
115
|
+
if safe_mode.nil?
|
116
|
+
mode = SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization] ? "unsafe" : "safe"
|
117
|
+
Kernel.warn "Called '#{method}' without the :safe option -- defaulting to #{mode} mode." unless SafeYAML::OPTIONS[:suppress_warnings]
|
118
|
+
safe_mode = !SafeYAML::OPTIONS[:enable_arbitrary_object_deserialization]
|
119
|
+
end
|
138
120
|
|
139
|
-
|
121
|
+
safe_mode
|
122
|
+
end
|
140
123
|
end
|
141
124
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "psych"
|
2
|
+
require "base64"
|
2
3
|
|
3
4
|
module SafeYAML
|
4
5
|
class PsychHandler < Psych::Handler
|
@@ -13,8 +14,8 @@ module SafeYAML
|
|
13
14
|
@result
|
14
15
|
end
|
15
16
|
|
16
|
-
def add_to_current_structure(value, anchor=nil, quoted=nil)
|
17
|
-
value = Transform.to_proper_type(value, quoted)
|
17
|
+
def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
|
18
|
+
value = Transform.to_proper_type(value, quoted, tag)
|
18
19
|
|
19
20
|
@anchors[anchor] = value if anchor
|
20
21
|
|
@@ -62,7 +63,7 @@ module SafeYAML
|
|
62
63
|
end
|
63
64
|
|
64
65
|
def scalar(value, anchor, tag, plain, quoted, style)
|
65
|
-
add_to_current_structure(value, anchor, quoted)
|
66
|
+
add_to_current_structure(value, anchor, quoted, tag)
|
66
67
|
end
|
67
68
|
|
68
69
|
def start_mapping(anchor, tag, implicit, style)
|
@@ -41,7 +41,7 @@ module SafeYAML
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def resolve_scalar(node)
|
44
|
-
Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)))
|
44
|
+
Transform.to_proper_type(node.value, QUOTE_STYLES.include?(node.instance_variable_get(:@style)), node.type_id)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/lib/safe_yaml/transform.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
1
3
|
module SafeYAML
|
2
4
|
class Transform
|
3
5
|
TRANSFORMERS = [
|
@@ -10,7 +12,7 @@ module SafeYAML
|
|
10
12
|
Transform::ToTime.new
|
11
13
|
]
|
12
14
|
|
13
|
-
def self.
|
15
|
+
def self.to_guessed_type(value, quoted=false)
|
14
16
|
return value if quoted
|
15
17
|
|
16
18
|
if value.is_a?(String)
|
@@ -22,5 +24,16 @@ module SafeYAML
|
|
22
24
|
|
23
25
|
value
|
24
26
|
end
|
27
|
+
|
28
|
+
def self.to_proper_type(value, quoted=false,tag=nil)
|
29
|
+
case tag
|
30
|
+
when "tag:yaml.org,2002:binary"
|
31
|
+
return Base64.decode64(value)
|
32
|
+
when "x-private:binary"
|
33
|
+
return Base64.decode64(value)
|
34
|
+
else
|
35
|
+
return self.to_guessed_type(value, quoted)
|
36
|
+
end
|
37
|
+
end
|
25
38
|
end
|
26
39
|
end
|
data/lib/safe_yaml/version.rb
CHANGED
@@ -6,10 +6,16 @@ rvm use 1.8.7@safe_yaml
|
|
6
6
|
rake spec
|
7
7
|
|
8
8
|
rvm use 1.9.2@safe_yaml
|
9
|
-
rake spec
|
9
|
+
YAMLER=syck rake spec
|
10
10
|
|
11
11
|
rvm use 1.9.3@safe_yaml
|
12
|
-
rake spec
|
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
|
13
19
|
|
14
20
|
rvm use 2.0.0@safe_yaml
|
15
|
-
rake spec
|
21
|
+
YAMLER=psych rake spec
|
data/spec/psych_handler_spec.rb
CHANGED
data/spec/safe_yaml_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe YAML do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
describe "unsafe_load" do
|
19
|
-
if RUBY_VERSION >= "1.9.3"
|
19
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
20
20
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
21
21
|
backdoor = YAML.unsafe_load("--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n")
|
22
22
|
backdoor.should be_exploited_through_setter
|
@@ -45,6 +45,33 @@ describe YAML do
|
|
45
45
|
object.should_not be_a(ExploitableBackDoor)
|
46
46
|
end
|
47
47
|
|
48
|
+
context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
|
49
|
+
if SafeYAML::YAML_ENGINE == "psych"
|
50
|
+
let(:arguments) {
|
51
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
52
|
+
["foo: bar", nil]
|
53
|
+
else
|
54
|
+
["foo: bar"]
|
55
|
+
end
|
56
|
+
}
|
57
|
+
it "uses Psych internally to parse YAML" do
|
58
|
+
stub_parser = stub(Psych::Parser)
|
59
|
+
Psych::Parser.stub(:new).and_return(stub_parser)
|
60
|
+
stub_parser.should_receive(:parse).with(*arguments)
|
61
|
+
# This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
|
62
|
+
YAML.safe_load(*arguments)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if SafeYAML::YAML_ENGINE == "syck"
|
67
|
+
it "uses Syck internally to parse YAML" do
|
68
|
+
YAML.should_receive(:parse).with("foo: bar")
|
69
|
+
# This won't work now; we just want to ensure YAML::parse was in fact called.
|
70
|
+
YAML.safe_load("foo: bar") rescue nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
48
75
|
it "loads a plain ol' YAML document just fine" do
|
49
76
|
result = YAML.safe_load <<-YAML.unindent
|
50
77
|
foo:
|
@@ -76,6 +103,30 @@ describe YAML do
|
|
76
103
|
result.should == [{}, {}, {}]
|
77
104
|
end
|
78
105
|
|
106
|
+
it "works for YAML documents with binary tagged keys" do
|
107
|
+
result = YAML.safe_load <<-YAML
|
108
|
+
? !!binary >
|
109
|
+
Zm9v
|
110
|
+
: "bar"
|
111
|
+
? !!binary >
|
112
|
+
YmFy
|
113
|
+
: "baz"
|
114
|
+
YAML
|
115
|
+
|
116
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "works for YAML documents with binary tagged values" do
|
120
|
+
result = YAML.safe_load <<-YAML
|
121
|
+
"foo": !!binary >
|
122
|
+
YmFy
|
123
|
+
"bar": !!binary >
|
124
|
+
YmF6
|
125
|
+
YAML
|
126
|
+
|
127
|
+
result.should == {"foo" => "bar", "bar" => "baz"}
|
128
|
+
end
|
129
|
+
|
79
130
|
it "works for YAML documents with sections" do
|
80
131
|
result = YAML.safe_load <<-YAML
|
81
132
|
mysql: &mysql
|
@@ -134,12 +185,14 @@ describe YAML do
|
|
134
185
|
end
|
135
186
|
|
136
187
|
describe "unsafe_load_file" do
|
137
|
-
if RUBY_VERSION >= "1.9.3"
|
188
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
|
138
189
|
it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
|
139
190
|
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.3.yaml"
|
140
191
|
backdoor.should be_exploited_through_setter
|
141
192
|
end
|
193
|
+
end
|
142
194
|
|
195
|
+
if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.2"
|
143
196
|
it "allows exploits through objects defined in YAML w/ !ruby/object via the :init_with method" do
|
144
197
|
backdoor = YAML.unsafe_load_file "spec/exploit.1.9.2.yaml"
|
145
198
|
backdoor.should be_exploited_through_init_with
|
@@ -166,7 +219,7 @@ describe YAML do
|
|
166
219
|
|
167
220
|
describe "load" do
|
168
221
|
let (:arguments) {
|
169
|
-
if
|
222
|
+
if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
|
170
223
|
["foo: bar", nil]
|
171
224
|
else
|
172
225
|
["foo: bar"]
|
data/spec/shared_specs.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,6 +4,12 @@ ROOT = File.join(HERE, "..")
|
|
4
4
|
$LOAD_PATH << File.join(ROOT, "lib")
|
5
5
|
$LOAD_PATH << File.join(HERE, "support")
|
6
6
|
|
7
|
+
if ENV["YAMLER"]
|
8
|
+
require "yaml"
|
9
|
+
YAML::ENGINE.yamler = ENV["YAMLER"]
|
10
|
+
puts "Running specs in Ruby #{RUBY_VERSION} with '#{YAML::ENGINE.yamler}' YAML engine."
|
11
|
+
end
|
12
|
+
|
7
13
|
require "safe_yaml"
|
8
14
|
require "heredoc_unindent"
|
9
15
|
|
data/spec/syck_resolver_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
2
|
|
3
3
|
describe SafeYAML::Transform::ToTime do
|
4
|
-
#
|
4
|
+
# It seems both Psych and Syck parse times starting w/ Ruby 1.9.2.
|
5
5
|
if RUBY_VERSION >= "1.9.2"
|
6
6
|
it "returns true when the value matches a valid Time" do
|
7
7
|
subject.transform?("2013-01-01 10:00:00")[0].should == true
|
metadata
CHANGED
@@ -1,23 +1,32 @@
|
|
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
|
+
hash: 3
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 7
|
9
|
+
- 0
|
10
|
+
version: 0.7.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Dan Tao
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
17
|
+
|
18
|
+
date: 2013-02-08 00:00:00 Z
|
13
19
|
dependencies: []
|
14
|
-
|
15
|
-
|
20
|
+
|
21
|
+
description: Parse YAML safely, without that pesky arbitrary object deserialization vulnerability
|
16
22
|
email: daniel.tao@gmail.com
|
17
23
|
executables: []
|
24
|
+
|
18
25
|
extensions: []
|
26
|
+
|
19
27
|
extra_rdoc_files: []
|
20
|
-
|
28
|
+
|
29
|
+
files:
|
21
30
|
- .gitignore
|
22
31
|
- .travis.yml
|
23
32
|
- Gemfile
|
@@ -52,32 +61,41 @@ files:
|
|
52
61
|
- spec/transform/to_symbol_spec.rb
|
53
62
|
- spec/transform/to_time_spec.rb
|
54
63
|
homepage: http://dtao.github.com/safe_yaml/
|
55
|
-
licenses:
|
64
|
+
licenses:
|
56
65
|
- MIT
|
57
66
|
post_install_message:
|
58
67
|
rdoc_options: []
|
59
|
-
|
68
|
+
|
69
|
+
require_paths:
|
60
70
|
- lib
|
61
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
72
|
none: false
|
63
|
-
requirements:
|
64
|
-
- -
|
65
|
-
- !ruby/object:Gem::Version
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 57
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 8
|
80
|
+
- 7
|
66
81
|
version: 1.8.7
|
67
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
83
|
none: false
|
69
|
-
requirements:
|
70
|
-
- -
|
71
|
-
- !ruby/object:Gem::Version
|
72
|
-
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
73
91
|
requirements: []
|
92
|
+
|
74
93
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.8.
|
94
|
+
rubygems_version: 1.8.25
|
76
95
|
signing_key:
|
77
96
|
specification_version: 3
|
78
|
-
summary: SameYAML provides an alternative implementation of YAML.load suitable for
|
79
|
-
|
80
|
-
test_files:
|
97
|
+
summary: SameYAML provides an alternative implementation of YAML.load suitable for accepting user input in Ruby applications.
|
98
|
+
test_files:
|
81
99
|
- spec/exploit.1.9.2.yaml
|
82
100
|
- spec/exploit.1.9.3.yaml
|
83
101
|
- spec/psych_handler_spec.rb
|