jruby_sandbox 0.2.2-java → 0.2.3-java

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.
@@ -0,0 +1,399 @@
1
+ require "fakefs/safe"
2
+
3
+ module Sandbox
4
+ TimeoutError = Class.new(Exception)
5
+
6
+ class Safe < Full
7
+ def activate!
8
+ activate_fakefs
9
+
10
+ keep_singleton_methods(:Kernel, KERNEL_S_METHODS)
11
+ keep_singleton_methods(:Symbol, SYMBOL_S_METHODS)
12
+ keep_singleton_methods(:String, STRING_S_METHODS)
13
+ keep_singleton_methods(:IO, IO_S_METHODS)
14
+
15
+ keep_methods(:Kernel, KERNEL_METHODS)
16
+ keep_methods(:NilClass, NILCLASS_METHODS)
17
+ keep_methods(:Symbol, SYMBOL_METHODS)
18
+ keep_methods(:TrueClass, TRUECLASS_METHODS)
19
+ keep_methods(:FalseClass, FALSECLASS_METHODS)
20
+ keep_methods(:Enumerable, ENUMERABLE_METHODS)
21
+ keep_methods(:String, STRING_METHODS)
22
+
23
+ # FIXME: Blacklisting Object methods is not a scalable solution.
24
+ # Whitelisting using #keep_methods is safer.
25
+ remove_method(:Object, :java_import)
26
+
27
+ Kernel.class_eval do
28
+ def `(*args)
29
+ raise NoMethodError, "` is unavailable"
30
+ end
31
+
32
+ def system(*args)
33
+ raise NoMethodError, "system is unavailable"
34
+ end
35
+ end
36
+ end
37
+
38
+ def activate_fakefs
39
+ require "fileutils"
40
+
41
+ # unfortunately, the authors of FakeFS used `extend self` in FileUtils, instead of `module_function`.
42
+ # I fixed it for them
43
+ (FakeFS::FileUtils.methods - Module.methods - Kernel.methods).each do |module_method_name|
44
+ FakeFS::FileUtils.send(:module_function, module_method_name)
45
+ end
46
+
47
+ import FakeFS
48
+ ref FakeFS::Dir
49
+ ref FakeFS::File
50
+ ref FakeFS::FileTest
51
+ import FakeFS::FileUtils #import FileUtils because it is a module
52
+
53
+ # this is basically what FakeFS.activate! does, but we want to do it in the sandbox
54
+ # so we have to live with this:
55
+ eval <<-RUBY
56
+ Object.class_eval do
57
+ remove_const(:Dir)
58
+ remove_const(:File)
59
+ remove_const(:FileTest)
60
+ remove_const(:FileUtils)
61
+
62
+ const_set(:Dir, FakeFS::Dir)
63
+ const_set(:File, FakeFS::File)
64
+ const_set(:FileUtils, FakeFS::FileUtils)
65
+ const_set(:FileTest, FakeFS::FileTest)
66
+ end
67
+
68
+ [Dir, File, FileUtils, FileTest].each do |fake_class|
69
+ fake_class.class_eval do
70
+ def self.class_eval
71
+ raise NoMethodError, "class_eval is unavailable"
72
+ end
73
+ def self.instance_eval
74
+ raise NoMethodError, "instance_eval is unavailable"
75
+ end
76
+ end
77
+ end
78
+ RUBY
79
+
80
+ FakeFS::FileSystem.clear
81
+ end
82
+
83
+ def eval(code, options={})
84
+ if seconds = options[:timeout]
85
+ sandbox_timeout(code, seconds) do
86
+ super code
87
+ end
88
+ else
89
+ super code
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def sandbox_timeout(name, seconds)
96
+ val, exc = nil
97
+
98
+ thread = Thread.start(name) do
99
+ begin
100
+ val = yield
101
+ rescue Exception => exc
102
+ end
103
+ end
104
+
105
+ thread.join(seconds)
106
+
107
+ if thread.alive?
108
+ if thread.respond_to? :kill!
109
+ thread.kill!
110
+ else
111
+ thread.kill
112
+ end
113
+
114
+ timed_out = true
115
+ end
116
+
117
+ if timed_out
118
+ raise TimeoutError, "#{self.class} timed out"
119
+ elsif exc
120
+ raise exc
121
+ else
122
+ val
123
+ end
124
+ end
125
+
126
+ IO_S_METHODS = %w[
127
+ new
128
+ foreach
129
+ open
130
+ ]
131
+
132
+ KERNEL_S_METHODS = %w[
133
+ Array
134
+ binding
135
+ block_given?
136
+ catch
137
+ chomp
138
+ chomp!
139
+ chop
140
+ chop!
141
+ eval
142
+ fail
143
+ Float
144
+ format
145
+ global_variables
146
+ gsub
147
+ gsub!
148
+ Integer
149
+ iterator?
150
+ lambda
151
+ local_variables
152
+ loop
153
+ method_missing
154
+ proc
155
+ raise
156
+ scan
157
+ split
158
+ sprintf
159
+ String
160
+ sub
161
+ sub!
162
+ throw
163
+ ].freeze
164
+
165
+ SYMBOL_S_METHODS = %w[
166
+ all_symbols
167
+ ].freeze
168
+
169
+ STRING_S_METHODS = %w[
170
+ new
171
+ ].freeze
172
+
173
+ KERNEL_METHODS = %w[
174
+ ==
175
+ ===
176
+ =~
177
+ Array
178
+ binding
179
+ block_given?
180
+ catch
181
+ chomp
182
+ chomp!
183
+ chop
184
+ chop!
185
+ class
186
+ clone
187
+ dup
188
+ eql?
189
+ equal?
190
+ eval
191
+ fail
192
+ Float
193
+ format
194
+ freeze
195
+ frozen?
196
+ global_variables
197
+ gsub
198
+ gsub!
199
+ hash
200
+ id
201
+ initialize_copy
202
+ inspect
203
+ instance_eval
204
+ instance_of?
205
+ instance_variables
206
+ instance_variable_get
207
+ instance_variable_set
208
+ instance_variable_defined?
209
+ Integer
210
+ is_a?
211
+ iterator?
212
+ kind_of?
213
+ lambda
214
+ local_variables
215
+ loop
216
+ methods
217
+ method_missing
218
+ nil?
219
+ private_methods
220
+ print
221
+ proc
222
+ protected_methods
223
+ public_methods
224
+ raise
225
+ remove_instance_variable
226
+ respond_to?
227
+ respond_to_missing?
228
+ scan
229
+ send
230
+ singleton_methods
231
+ singleton_method_added
232
+ singleton_method_removed
233
+ singleton_method_undefined
234
+ split
235
+ sprintf
236
+ String
237
+ sub
238
+ sub!
239
+ taint
240
+ tainted?
241
+ throw
242
+ to_a
243
+ to_s
244
+ type
245
+ untaint
246
+ __send__
247
+ ].freeze
248
+
249
+ NILCLASS_METHODS = %w[
250
+ &
251
+ inspect
252
+ nil?
253
+ to_a
254
+ to_f
255
+ to_i
256
+ to_s
257
+ ^
258
+ |
259
+ ].freeze
260
+
261
+ SYMBOL_METHODS = %w[
262
+ ===
263
+ id2name
264
+ inspect
265
+ to_i
266
+ to_int
267
+ to_s
268
+ to_sym
269
+ ].freeze
270
+
271
+ TRUECLASS_METHODS = %w[
272
+ &
273
+ to_s
274
+ ^
275
+ |
276
+ ].freeze
277
+
278
+ FALSECLASS_METHODS = %w[
279
+ &
280
+ to_s
281
+ ^
282
+ |
283
+ ].freeze
284
+
285
+ ENUMERABLE_METHODS = %w[
286
+ all?
287
+ any?
288
+ collect
289
+ detect
290
+ each_with_index
291
+ entries
292
+ find
293
+ find_all
294
+ grep
295
+ include?
296
+ inject
297
+ map
298
+ max
299
+ member?
300
+ min
301
+ partition
302
+ reject
303
+ select
304
+ sort
305
+ sort_by
306
+ to_a
307
+ zip
308
+ ].freeze
309
+
310
+ STRING_METHODS = %w[
311
+ %
312
+ *
313
+ +
314
+ <<
315
+ <=>
316
+ ==
317
+ =~
318
+ capitalize
319
+ capitalize!
320
+ casecmp
321
+ center
322
+ chomp
323
+ chomp!
324
+ chop
325
+ chop!
326
+ concat
327
+ count
328
+ crypt
329
+ delete
330
+ delete!
331
+ downcase
332
+ downcase!
333
+ dump
334
+ each
335
+ each_byte
336
+ each_line
337
+ empty?
338
+ eql?
339
+ gsub
340
+ gsub!
341
+ hash
342
+ hex
343
+ include?
344
+ index
345
+ initialize
346
+ initialize_copy
347
+ insert
348
+ inspect
349
+ intern
350
+ length
351
+ ljust
352
+ lines
353
+ lstrip
354
+ lstrip!
355
+ match
356
+ next
357
+ next!
358
+ oct
359
+ replace
360
+ reverse
361
+ reverse!
362
+ rindex
363
+ rjust
364
+ rstrip
365
+ rstrip!
366
+ scan
367
+ size
368
+ slice
369
+ slice!
370
+ split
371
+ squeeze
372
+ squeeze!
373
+ strip
374
+ strip!
375
+ start_with?
376
+ sub
377
+ sub!
378
+ succ
379
+ succ!
380
+ sum
381
+ swapcase
382
+ swapcase!
383
+ to_f
384
+ to_i
385
+ to_s
386
+ to_str
387
+ to_sym
388
+ tr
389
+ tr!
390
+ tr_s
391
+ tr_s!
392
+ upcase
393
+ upcase!
394
+ upto
395
+ []
396
+ []=
397
+ ].freeze
398
+ end
399
+ end
@@ -1,3 +1,3 @@
1
1
  module Sandbox
2
- VERSION = '0.2.2'
2
+ VERSION = "0.2.3"
3
3
  end
@@ -1,8 +1,9 @@
1
- require 'rspec'
2
- require 'sandbox'
1
+ require "rspec"
2
+ require "sandbox"
3
+ require "tempfile"
3
4
 
4
5
  class OutsideFoo
5
- def self.bar; 'bar'; end
6
+ def self.bar; "bar"; end
6
7
  end
7
8
 
8
9
  describe "Sandbox exploits" do
@@ -12,166 +13,162 @@ describe "Sandbox exploits" do
12
13
  subject.activate!
13
14
  end
14
15
 
15
- it 'should not allow access to the filesystem using backticks' do
16
+ it "should not allow access to the filesystem using backticks" do
16
17
  expect {
17
- subject.eval('`cat spec/support/foo.txt`')
18
+ subject.eval(%|`cat spec/support/foo.txt`|)
18
19
  }.to raise_error(Sandbox::SandboxException)
19
20
  end
20
21
 
21
22
  it "should not allow running system commands using system" do
22
23
  expect {
23
- subject.eval 'system("ls")'
24
+ subject.eval(%|system("ls")|)
24
25
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
25
26
  end
26
27
 
27
28
  it "should not allow running system commands through File.class_eval" do
28
29
  expect {
29
- subject.eval 'File.class_eval { `echo Hello` }'
30
+ subject.eval(%|File.class_eval { `echo Hello` }|)
30
31
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
31
32
 
32
33
  expect {
33
- subject.eval 'FileUtils.class_eval { `echo Hello` }'
34
+ subject.eval(%|FileUtils.class_eval { `echo Hello` }|)
34
35
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
35
36
 
36
37
  expect {
37
- subject.eval 'Dir.class_eval { `echo Hello` }'
38
+ subject.eval(%|Dir.class_eval { `echo Hello` }|)
38
39
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
39
40
 
40
41
  expect {
41
- subject.eval 'FileTest.class_eval { `echo Hello` }'
42
+ subject.eval(%|FileTest.class_eval { `echo Hello` }|)
42
43
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
43
44
  end
44
45
 
45
46
  it "should not allow running system commands through File.eval" do
46
47
  expect {
47
- subject.eval 'File.eval "`echo Hello`"'
48
+ subject.eval(%|File.eval "`echo Hello`"|)
48
49
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
49
50
 
50
51
  expect {
51
- subject.eval 'FileUtils.eval "`echo Hello`"'
52
+ subject.eval(%|FileUtils.eval "`echo Hello`"|)
52
53
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
53
54
 
54
55
  expect {
55
- subject.eval 'Dir.eval "`echo Hello`"'
56
+ subject.eval(%|Dir.eval "`echo Hello`"|)
56
57
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
57
58
 
58
59
  expect {
59
- subject.eval 'FileTest.eval "`echo Hello`"'
60
+ subject.eval(%|FileTest.eval "`echo Hello`"|)
60
61
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
61
62
  end
62
63
 
63
64
  it "should not allow running system commands through File.instance_eval" do
64
65
  expect {
65
- subject.eval 'File.instance_eval { `echo Hello` }'
66
+ subject.eval(%|File.instance_eval { `echo Hello` }|)
66
67
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
67
68
 
68
69
  expect {
69
- subject.eval 'FileUtils.instance_eval { `echo Hello` }'
70
+ subject.eval(%|FileUtils.instance_eval { `echo Hello` }|)
70
71
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
71
72
 
72
73
  expect {
73
- subject.eval 'Dir.instance_eval { `echo Hello` }'
74
+ subject.eval(%|Dir.instance_eval { `echo Hello` }|)
74
75
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
75
76
 
76
77
  expect {
77
- subject.eval 'FileTest.instance_eval { `echo Hello` }'
78
+ subject.eval(%|FileTest.instance_eval { `echo Hello` }|)
78
79
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
79
80
  end
80
81
 
81
82
  it "should not allow running any commands or reading files using IO" do
82
83
  expect {
83
- subject.eval 'f=IO.popen("uname"); f.readlines; f.close'
84
+ subject.eval(%|f=IO.popen("uname"); f.readlines; f.close|)
84
85
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
85
86
 
86
87
  expect {
87
- subject.eval 'IO.binread("/etc/passwd")'
88
+ subject.eval(%|IO.binread("/etc/passwd")|)
88
89
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
89
90
 
90
91
  expect {
91
- subject.eval 'IO.read("/etc/passwd")'
92
+ subject.eval(%|IO.read("/etc/passwd")|)
92
93
  }.to raise_error(Sandbox::SandboxException, /NoMethodError/)
93
94
  end
94
95
 
95
96
  it "should not pass through methods added to Kernel" do
96
- k = subject.eval("Kernel")
97
+ k = subject.eval(%|Kernel|)
97
98
  def k.crack
98
99
  open("/etc/passwd")
99
100
  end
100
101
 
101
102
  Kernel.should respond_to(:crack)
102
- subject.eval("Kernel.respond_to?(:crack)").should == false
103
+ subject.eval(%|Kernel.respond_to?(:crack)|).should == false
103
104
  end
104
105
 
105
106
  it "should not allow calling fork on Kernel, even through eval" do
106
- subject.eval("eval('Kernel').respond_to?(:fork)").should == false
107
+ subject.eval(%|eval("Kernel").respond_to?(:fork)|).should == false
107
108
  end
108
109
 
109
110
  it "should not get access to outside the box objects by using eval and TOPLEVEL_BINDING" do
110
111
  expect {
111
- subject.eval(%{eval('OutsideFoo.bar', TOPLEVEL_BINDING)})
112
+ subject.eval(%{eval("OutsideFoo.bar", TOPLEVEL_BINDING)})
112
113
  }.to raise_error(Sandbox::SandboxException, /NameError/)
113
114
  end
114
115
 
115
116
  it "should not get access to the outside eval through a ref'd object" do
116
- subject.ref OutsideFoo
117
- subject.eval "obj = OutsideFoo.new"
118
- subject.eval("(obj.methods.grep /^eval/).empty?").should == true
119
- subject.eval("obj.respond_to?(:eval)").should == false
117
+ subject.ref(OutsideFoo)
118
+ subject.eval(%|obj = OutsideFoo.new|)
119
+ subject.eval(%|(obj.methods.grep /^eval/).empty?|).should == true
120
+ subject.eval(%|obj.respond_to?(:eval)|).should == false
120
121
  end
121
122
 
122
123
  it "should not allow file access, even through a ref hack" do
123
- unsafe_open = %{File.open('/etc/passwd').read}
124
-
125
124
  expect {
126
- subject.eval(unsafe_open)
125
+ subject.eval(%|File.open("/etc/passwd").read|)
127
126
  }.to raise_error(Sandbox::SandboxException)
128
127
 
129
- subject.ref OutsideFoo
130
- subject.eval "obj = OutsideFoo.new"
131
-
132
- unsafe_open_hack = %{obj.eval "#{unsafe_open}"}
128
+ subject.ref(OutsideFoo)
129
+ subject.eval(%|obj = OutsideFoo.new|)
133
130
 
134
131
  pending "gotta figure out how to lock down ref'd objects eval" do
135
132
  expect {
136
- subject.eval(unsafe_open_hack)
133
+ subject.eval(%|obj.eval("File.open(\\"/etc/passwd\\").read")|)
137
134
  }.to raise_error(Sandbox::SandboxException)
138
135
  end
139
136
  end
140
137
 
141
138
  it "should have safe globals" do
142
- subject.eval('$0').should == '(sandbox)'
139
+ subject.eval(%|$0|).should == "(sandbox)"
143
140
  /(.)(.)(.)/.match("abc")
144
- subject.eval("$TEST = 'TEST'; $TEST").should == "TEST"
145
- subject.eval("/(.)(.)(.)/.match('def'); $2").should == "e"
141
+ subject.eval(%|$TEST = "TEST"; $TEST|).should == "TEST"
142
+ subject.eval(%|/(.)(.)(.)/.match("def"); $2|).should == "e"
146
143
  $2.should == "b"
147
- subject.eval("$TEST").should == "TEST"
148
- subject.eval("$2").should == "e"
144
+ subject.eval(%|$TEST|).should == "TEST"
145
+ subject.eval(%|$2|).should == "e"
149
146
  end
150
147
 
151
148
  it "should not keep Kernel.fork" do
152
149
  expect {
153
- subject.eval("Kernel.fork")
150
+ subject.eval(%|Kernel.fork|)
154
151
  }.to raise_error(Sandbox::SandboxException)
155
152
 
156
153
  expect {
157
- subject.eval("fork")
154
+ subject.eval(%|fork|)
158
155
  }.to raise_error(Sandbox::SandboxException)
159
156
  end
160
157
 
161
158
  it "should not allow the sandbox to get back to Kernel through ancestors" do
162
- subject.eval('$0.class.ancestors[3].respond_to?(:fork)').should == false
159
+ subject.eval(%|$0.class.ancestors[3].respond_to?(:fork)|).should == false
163
160
  end
164
161
 
165
162
  it "should not pass through block scope" do
166
163
  1.times do |i|
167
- subject.eval('local_variables').should == []
164
+ subject.eval(%|local_variables|).should == []
168
165
  end
169
166
  end
170
167
 
171
168
  it "should not allow exploits through match data" do
172
- subject.eval("begin; /(.+)/.match('FreakyFreaky').instance_eval { open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError
169
+ subject.eval(%|begin; /(.+)/.match("FreakyFreaky").instance_eval { open("/etc/passwd") }; rescue NameError; :NameError; end|).should == :NameError
173
170
 
174
- subject.eval("begin;(begin;Regexp.new('(');rescue e;e;end).instance_eval{ open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError
171
+ subject.eval(%|begin;(begin;Regexp.new("(");rescue e;e;end).instance_eval{ open("/etc/passwd") }; rescue NameError; :NameError; end|).should == :NameError
175
172
  end
176
173
 
177
174
  it "should not be able to access outside box Kernel through exceptions" do
@@ -184,6 +181,16 @@ describe "Sandbox exploits" do
184
181
  RUBY
185
182
  subject.eval(exception_code)
186
183
 
187
- subject.eval('obj.class.ancestors[4].respond_to?(:fork)').should == false
184
+ subject.eval(%|obj.class.ancestors[4].respond_to?(:fork)|).should == false
185
+ end
186
+
187
+ it "should not allow access to Java classes" do
188
+ tempfile = Tempfile.new("sandbox")
189
+ expect {
190
+ subject.eval("Object.send(:java_import, 'java.lang.ProcessBuilder')")
191
+ subject.eval("Java::java.lang.ProcessBuilder.new('sh', '-c', 'echo pwned > #{tempfile.path}').start; nil")
192
+ }.to raise_error(Sandbox::SandboxException)
193
+ tempfile.size.should == 0
194
+ tempfile.close
188
195
  end
189
196
  end