require 'rspec' require 'sandbox' class OutsideFoo def self.bar; 'bar'; end end describe "Sandbox exploits" do subject { Sandbox.safe } before(:each) do subject.activate! end it 'should not allow access to the filesystem using backticks' do expect { subject.eval('`cat spec/support/foo.txt`') }.to raise_error(Sandbox::SandboxException) end it "should not allow running system commands through File.class_eval" do expect { subject.eval 'File.class_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'FileUtils.class_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'Dir.class_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'FileTest.class_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) end it "should not allow running system commands through File.instance_eval" do expect { subject.eval 'File.instance_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'FileUtils.instance_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'Dir.instance_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'FileTest.instance_eval { `echo Hello` }' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) end it "should not allow running any commands or reading files using IO" do expect { subject.eval 'f=IO.popen("uname"); f.readlines; f.close' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'IO.binread("/etc/passwd")' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval 'IO.read("/etc/passwd")' }.to raise_error(Sandbox::SandboxException, /NoMethodError/) end it "should not pass through methods added to Kernel" do k = subject.eval("Kernel") def k.crack open("/etc/passwd") end Kernel.should respond_to(:crack) subject.eval("Kernel.respond_to?(:crack)").should == false end it "should not allow calling fork on Kernel, even through eval" do subject.eval("eval('Kernel').respond_to?(:fork)").should == false end it "should not get access to outside the box objects by using eval and TOPLEVEL_BINDING" do expect { subject.eval(%{eval('OutsideFoo.bar', TOPLEVEL_BINDING)}) }.to raise_error(Sandbox::SandboxException, /NameError/) end it "should not get access to the outside eval through a ref'd object" do subject.ref OutsideFoo subject.eval "obj = OutsideFoo.new" subject.eval("(obj.methods.grep /^eval/).empty?").should == true subject.eval("obj.respond_to?(:eval)").should == false end it "should not allow file access, even through a ref hack" do unsafe_open = %{File.open('/etc/passwd').read} expect { subject.eval(unsafe_open) }.to raise_error(Sandbox::SandboxException) subject.ref OutsideFoo subject.eval "obj = OutsideFoo.new" unsafe_open_hack = %{obj.eval "#{unsafe_open}"} pending "gotta figure out how to lock down ref'd objects eval" do expect { subject.eval(unsafe_open_hack) }.to raise_error(Sandbox::SandboxException) end end it "should have safe globals" do subject.eval('$0').should == '(sandbox)' /(.)(.)(.)/.match("abc") subject.eval("$TEST = 'TEST'; $TEST").should == "TEST" subject.eval("/(.)(.)(.)/.match('def'); $2").should == "e" $2.should == "b" subject.eval("$TEST").should == "TEST" subject.eval("$2").should == "e" end it "should not keep Kernel.fork" do expect { subject.eval("Kernel.fork") }.to raise_error(Sandbox::SandboxException) expect { subject.eval("fork") }.to raise_error(Sandbox::SandboxException) end it "should not allow the sandbox to get back to Kernel through ancestors" do subject.eval('$0.class.ancestors[3].respond_to?(:fork)').should == false end it "should not pass through block scope" do 1.times do |i| subject.eval('local_variables').should == [] end end it "should not allow exploits through match data" do subject.eval("begin; /(.+)/.match('FreakyFreaky').instance_eval { open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError subject.eval("begin;(begin;Regexp.new('(');rescue e;e;end).instance_eval{ open('/etc/passwd') }; rescue NameError; :NameError; end").should == :NameError end it "should not be able to access outside box Kernel through exceptions" do exception_code = <<-RUBY begin raise rescue => e obj = e end RUBY subject.eval(exception_code) subject.eval('obj.class.ancestors[4].respond_to?(:fork)').should == false end end