safe_yaml 0.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/Gemfile +9 -0
- data/Gemfile.lock +28 -0
- data/Rakefile +6 -0
- data/lib/handler.rb +86 -0
- data/lib/safe_yaml.rb +10 -0
- data/lib/version.rb +3 -0
- data/safe_yaml.gemspec +16 -0
- data/spec/handler_spec.rb +108 -0
- data/spec/safe_yaml_spec.rb +57 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/exploitable_back_door.rb +23 -0
- metadata +62 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
safe_yaml (0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
diff-lcs (1.1.3)
|
10
|
+
heredoc_unindent (1.1.2)
|
11
|
+
rake (10.0.3)
|
12
|
+
rspec (2.12.0)
|
13
|
+
rspec-core (~> 2.12.0)
|
14
|
+
rspec-expectations (~> 2.12.0)
|
15
|
+
rspec-mocks (~> 2.12.0)
|
16
|
+
rspec-core (2.12.2)
|
17
|
+
rspec-expectations (2.12.1)
|
18
|
+
diff-lcs (~> 1.1.3)
|
19
|
+
rspec-mocks (2.12.1)
|
20
|
+
|
21
|
+
PLATFORMS
|
22
|
+
ruby
|
23
|
+
|
24
|
+
DEPENDENCIES
|
25
|
+
heredoc_unindent
|
26
|
+
rake
|
27
|
+
rspec
|
28
|
+
safe_yaml!
|
data/Rakefile
ADDED
data/lib/handler.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module SafeYAML
|
4
|
+
class Handler < Psych::Handler
|
5
|
+
def initialize
|
6
|
+
@stack = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def result
|
10
|
+
@result
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_to_current_structure(value)
|
14
|
+
if @result.nil?
|
15
|
+
@result = value
|
16
|
+
@current_structure = @result
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
case @current_structure
|
21
|
+
when Array
|
22
|
+
@current_structure.push(transform_value(value))
|
23
|
+
|
24
|
+
when Hash
|
25
|
+
if @current_key.nil?
|
26
|
+
@current_key = transform_value(value)
|
27
|
+
else
|
28
|
+
@current_structure[@current_key] = transform_value(value)
|
29
|
+
@current_key = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
else
|
33
|
+
raise "Don't know how to add to a #{@current_structure.class}!"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def transform_value(value)
|
38
|
+
if value.is_a?(String)
|
39
|
+
if value.match(/^:\w+$/)
|
40
|
+
return value[1..-1].to_sym
|
41
|
+
|
42
|
+
elsif value.match(/^\d+$/)
|
43
|
+
return value.to_i
|
44
|
+
|
45
|
+
elsif value.match(/^\d+(?:\.\d*)?$/) || value.match(/^\.\d+$/)
|
46
|
+
return value.to_f
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def streaming?
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# event handlers
|
58
|
+
def scalar(value, anchor, tag, plain, quoted, style)
|
59
|
+
add_to_current_structure(value)
|
60
|
+
end
|
61
|
+
|
62
|
+
def start_mapping(*args) # anchor, tag, implicit, style
|
63
|
+
map = {}
|
64
|
+
self.add_to_current_structure(map)
|
65
|
+
@current_structure = map
|
66
|
+
@stack.push(map)
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_mapping
|
70
|
+
@stack.pop
|
71
|
+
@current_structure = @stack.last
|
72
|
+
end
|
73
|
+
|
74
|
+
def start_sequence(*args) # anchor, tag, implicit, style
|
75
|
+
seq = []
|
76
|
+
self.add_to_current_structure(seq)
|
77
|
+
@current_structure = seq
|
78
|
+
@stack.push(seq)
|
79
|
+
end
|
80
|
+
|
81
|
+
def end_sequence
|
82
|
+
@stack.pop
|
83
|
+
@current_structure = @stack.last
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/safe_yaml.rb
ADDED
data/lib/version.rb
ADDED
data/safe_yaml.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "safe_yaml"
|
6
|
+
gem.authors = ["Dan Tao"]
|
7
|
+
gem.email = ["daniel.tao@gmail.com"]
|
8
|
+
gem.description = %q{Parse (simple) YAML safely, without that pesky arbitrary code execution vulnerability.}
|
9
|
+
gem.summary = %q{SameYAML adds a ::safe_load method to Ruby's built-in YAML module to parse YAML data for only basic types (strings, symbols, numbers, arrays, and hashes).}
|
10
|
+
gem.homepage = "http://dtao.github.com/safe_yaml/"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.test_files = gem.files.grep(%r{^spec/})
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = SafeYAML::VERSION
|
16
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require "handler"
|
4
|
+
|
5
|
+
describe SafeYAML::Handler do
|
6
|
+
let(:handler) { SafeYAML::Handler.new }
|
7
|
+
let(:parser) { Psych::Parser.new(handler) }
|
8
|
+
let(:result) { handler.result }
|
9
|
+
|
10
|
+
def parse(yaml)
|
11
|
+
parser.parse(yaml.unindent)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "translates most values to strings" do
|
15
|
+
parser.parse "key: value"
|
16
|
+
result.should == { "key" => "value" }
|
17
|
+
end
|
18
|
+
|
19
|
+
it "translates values starting with ':' to symbols" do
|
20
|
+
parser.parse ":key: value"
|
21
|
+
result.should == { :key => "value" }
|
22
|
+
end
|
23
|
+
|
24
|
+
it "translates valid integral numbers to integers" do
|
25
|
+
parser.parse "integer: 1"
|
26
|
+
result.should == { "integer" => 1 }
|
27
|
+
end
|
28
|
+
|
29
|
+
it "translates valid decimal numbers to floats" do
|
30
|
+
parser.parse "float: 3.14"
|
31
|
+
result.should == { "float" => 3.14 }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "applies the same transformations to values as to keys" do
|
35
|
+
parse <<-YAML
|
36
|
+
string: value
|
37
|
+
symbol: :value
|
38
|
+
integer: 1
|
39
|
+
float: 3.14
|
40
|
+
YAML
|
41
|
+
|
42
|
+
result.should == {
|
43
|
+
"string" => "value",
|
44
|
+
"symbol" => :value,
|
45
|
+
"integer" => 1,
|
46
|
+
"float" => 3.14
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it "translates sequences to arrays" do
|
51
|
+
parse <<-YAML
|
52
|
+
- foo
|
53
|
+
- bar
|
54
|
+
- baz
|
55
|
+
YAML
|
56
|
+
|
57
|
+
result.should == ["foo", "bar", "baz"]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "applies the same transformations to elements in sequences as to all values" do
|
61
|
+
parse <<-YAML
|
62
|
+
- string
|
63
|
+
- :symbol
|
64
|
+
- 1
|
65
|
+
- 3.14
|
66
|
+
YAML
|
67
|
+
|
68
|
+
result.should == ["string", :symbol, 1, 3.14]
|
69
|
+
end
|
70
|
+
|
71
|
+
it "translates maps to hashes" do
|
72
|
+
parse <<-YAML
|
73
|
+
foo: blah
|
74
|
+
bar: glah
|
75
|
+
baz: flah
|
76
|
+
YAML
|
77
|
+
|
78
|
+
result.should == {
|
79
|
+
"foo" => "blah",
|
80
|
+
"bar" => "glah",
|
81
|
+
"baz" => "flah"
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
it "applies the same transformations to values in hashes as to all values" do
|
86
|
+
parse <<-YAML
|
87
|
+
foo: :symbol
|
88
|
+
bar: 1
|
89
|
+
baz: 3.14
|
90
|
+
YAML
|
91
|
+
|
92
|
+
result.should == {
|
93
|
+
"foo" => :symbol,
|
94
|
+
"bar" => 1,
|
95
|
+
"baz" => 3.14
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
it "deals just fine with nested maps" do
|
100
|
+
parse <<-YAML
|
101
|
+
foo:
|
102
|
+
bar:
|
103
|
+
marco: polo
|
104
|
+
YAML
|
105
|
+
|
106
|
+
result.should == { "foo" => { "bar" => { "marco" => "polo" } } }
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "spec_helper")
|
2
|
+
|
3
|
+
require "safe_yaml"
|
4
|
+
require "exploitable_back_door"
|
5
|
+
|
6
|
+
describe YAML do
|
7
|
+
before :each do
|
8
|
+
ExploitableBackDoor.reset
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "load" do
|
12
|
+
if RUBY_VERSION >= "1.9.3"
|
13
|
+
it "allows exploits through objects defined in YAML w/ !ruby/hash" do
|
14
|
+
YAML.load "--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n"
|
15
|
+
ExploitableBackDoor.should be_exploited
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "allows exploits through objects defined in YAML w/ !ruby/object" do
|
20
|
+
YAML.load "--- !ruby/object:ExploitableBackDoor\nfoo: bar\n"
|
21
|
+
ExploitableBackDoor.should be_exploited
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "safe_load" do
|
26
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/object" do
|
27
|
+
YAML.safe_load "--- !ruby/object:ExploitableBackDoor\nfoo: bar\n"
|
28
|
+
ExploitableBackDoor.should_not be_exploited
|
29
|
+
end
|
30
|
+
|
31
|
+
it "does NOT allow exploits through objects defined in YAML w/ !ruby/hash" do
|
32
|
+
YAML.safe_load "--- !ruby/hash:ExploitableBackDoor\nfoo: bar\n"
|
33
|
+
ExploitableBackDoor.should_not be_exploited
|
34
|
+
end
|
35
|
+
|
36
|
+
it "loads a plain ol' YAML document just fine" do
|
37
|
+
result = YAML.safe_load <<-YAML.unindent
|
38
|
+
foo:
|
39
|
+
number: 1
|
40
|
+
string: Hello, there!
|
41
|
+
symbol: :blah
|
42
|
+
sequence:
|
43
|
+
- hi
|
44
|
+
- bye
|
45
|
+
YAML
|
46
|
+
|
47
|
+
result.should == {
|
48
|
+
"foo" => {
|
49
|
+
"number" => 1,
|
50
|
+
"string" => "Hello, there!",
|
51
|
+
"symbol" => :blah,
|
52
|
+
"sequence" => ["hi", "bye"]
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
class ExploitableBackDoor
|
2
|
+
@@exploited = false
|
3
|
+
|
4
|
+
def self.exploited?
|
5
|
+
@@exploited
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.reset
|
9
|
+
@@exploited = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def init_with(command)
|
13
|
+
# Note: this is how bad this COULD be.
|
14
|
+
# system("#{command}")
|
15
|
+
@@exploited = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(command, arguments)
|
19
|
+
# Note: this is how bad this COULD be.
|
20
|
+
# system("#{command} #{arguments}")
|
21
|
+
@@exploited = true
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: safe_yaml
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dan Tao
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Parse (simple) YAML safely, without that pesky arbitrary code execution
|
15
|
+
vulnerability.
|
16
|
+
email:
|
17
|
+
- daniel.tao@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- Rakefile
|
25
|
+
- lib/handler.rb
|
26
|
+
- lib/safe_yaml.rb
|
27
|
+
- lib/version.rb
|
28
|
+
- safe_yaml.gemspec
|
29
|
+
- spec/handler_spec.rb
|
30
|
+
- spec/safe_yaml_spec.rb
|
31
|
+
- spec/spec_helper.rb
|
32
|
+
- spec/support/exploitable_back_door.rb
|
33
|
+
homepage: http://dtao.github.com/safe_yaml/
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.24
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: SameYAML adds a ::safe_load method to Ruby's built-in YAML module to parse
|
57
|
+
YAML data for only basic types (strings, symbols, numbers, arrays, and hashes).
|
58
|
+
test_files:
|
59
|
+
- spec/handler_spec.rb
|
60
|
+
- spec/safe_yaml_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
- spec/support/exploitable_back_door.rb
|