jruby_sandbox 0.2.2-java → 0.2.3-java

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