require "rspec" require "sandbox" require "tempfile" 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 using system" do expect { subject.eval(%|system("ls")|) }.to raise_error(Sandbox::SandboxException, /NoMethodError/) 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.eval" do expect { subject.eval(%|File.eval "`echo Hello`"|) }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval(%|FileUtils.eval "`echo Hello`"|) }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval(%|Dir.eval "`echo Hello`"|) }.to raise_error(Sandbox::SandboxException, /NoMethodError/) expect { subject.eval(%|FileTest.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 expect { subject.eval(%|File.open("/etc/passwd").read|) }.to raise_error(Sandbox::SandboxException) subject.ref(OutsideFoo) subject.eval(%|obj = OutsideFoo.new|) # pending "gotta figure out how to lock down ref'd objects eval" do # expect { # subject.eval(%|obj.eval("File.open(\\"/etc/passwd\\").read")|) # }.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 it "should not allow access to Java classes" do tempfile = Tempfile.new("sandbox") expect { subject.eval("Object.send(:java_import, 'java.lang.ProcessBuilder')") subject.eval("Java::java.lang.ProcessBuilder.new('sh', '-c', 'echo pwned > #{tempfile.path}').start; nil") }.to raise_error(Sandbox::SandboxException) tempfile.size.should == 0 tempfile.close end end