safe_yaml 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "heredoc_unindent"
7
+ gem "rake"
8
+ gem "rspec"
9
+ end
@@ -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!
@@ -0,0 +1,6 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ desc "Run specs"
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.rspec_opts = %w(--color --format d)
6
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ require "yaml"
2
+ require "handler"
3
+
4
+ module YAML
5
+ def self.safe_load(yaml)
6
+ safe_handler = SafeYAML::Handler.new
7
+ Psych::Parser.new(safe_handler).parse(yaml)
8
+ return safe_handler.result
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module SafeYAML
2
+ VERSION = "0.1"
3
+ end
@@ -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
@@ -0,0 +1,7 @@
1
+ HERE = File.dirname(__FILE__)
2
+ ROOT = File.join(HERE, "..")
3
+
4
+ $LOAD_PATH << File.join(ROOT, "lib")
5
+ $LOAD_PATH << File.join(HERE, "support")
6
+
7
+ require "heredoc_unindent"
@@ -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