xplenty-jruby_sandbox 0.2.4-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,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