xplenty-jruby_sandbox 0.2.4-java

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