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