Sandbox: Security
A SandBox has two use cases: 1. Unit Testing of code with mocks or stubs, and also, allowing testing with different environments. 2. The secure running of 3rd party code inside a 1st party application. For the second use case, I will use a fictional blogging software called "Hot Blog" as the example. Hot Blog is a very popular Open Source blogging platform. Hot Blog allows third party developers to write plugins. While many Hot Blog plugin developers have the best of intentions, some of them are novice coders that make security mistakes. So let's talk about how Hot Blog could benefit from using the new SandBox API. By default, a SandBox instance is a blank slate. There's nothing inside of it, unless the SandBox is told to have something in it. That means that sandboxed code that tries to read $_SESSION will find an empty array. Same with $_SERVER, $_POST, and $_GET. That's by default. This allows the code that controls the sandbox to create custom access to application level resources. Let's say that Hot Blog wants plugin developers to be able to access certain $_POST variables, but only *after* Hot Blog has checked the strings for multi-byte attack vulnerabilities. To do this, Hot Blog creates a class called PluginAPI with a GetCleanPost method. This lets sandboxed plugins get $_POST data without being able to bypass Hot Blog's mandatory security check. (Remember, $_POST is empty inside the sandbox). The code looks like this: $oSandbox = new SPLSandBox(); $oSandbox->MockClass('\HotBlog\PluginAPI','\HotBlog\PluginAPI'); $oUserPlugin = $oSandbox->GetInstance('BobsMagicPlugin'); $oUserPlugin->Run(); Because "Bob" has written his plugin as a Hot Blog plugin and knows that Hot Blog's rules require him to use \HotBlog\PluginAPI::GetCleanPost() to access a $_POST variable, he calls that instead of using $_POST. Now, Hot Blog can impose mandatory security checks on incoming data making their application more secure. Next, let's talk about includes. By default, if sandboxed code tries to include or require a file, a SandBoxAccessViolation is thrown. Letting sandboxed code include whatever it wants defeats the point of a sandbox, at least for security use cases. Of course, includes are useful, and plugins may need them. But the outer application should be able to control that access. Enter SPLSandBox::RegisterIncludeHandler(). RegisterIncludeHandler accepts a callable. The callable's signature is: (int $IncludeType, string $FileName, string $FilePath) Where: $IncludeType is: 0 - require 1 - require_once 2 - include 3 - include_once $FileName is the file without the path, and $FilePath is the path with trailing `/`. If the sandbox should allow includes, the sandbox should have an Include Handler registered. The SandBox API calls the include handler, if defined, when sandboxed code tries to include or require files. Let's setup a function so our plugin authors can include files, but only from their own plugin directory: // Sandbox setup for includes: $oSandbox = new SPLSandBox(); $oSandbox->RegisterIncludeHandler('HotBlogInclude'); $oUserPlugin = $oSandbox->GetInstance('BobsMagicPlugin'); $oUserPlugin->Run(); // Include Handler: function HotBlogInclude($Type, $FileName, $FilePath){ if(file_exists($PluginDirectoy.$FileName)){ $oSandbox->Include($PluginDirectoy.$FileName); return 0; } return 1; // error! } In the above example, $FilePath contained the path that Bob requested with his include statement. But we ignored it! Bob is only allowed to include from his plugin's own directory, so we see if the file is in $PluginDirectoy instead. If the file is in Bob's directory, we include it *into* the sandbox with SPLSandBox::Include(), making it available to Bob's code, but keeping the main application code clean of any registrations the include may cause. ** Back to Unit Testing ** For the Unit Testing use case, however, certain code under test may normally read from $_GET, and that shouldn't change under test. In this next example, we are running a unit test on a FrontController class, and we want to see if it works with many different URL structures. Normally, the web server will map example.com/a/b/c to $_GET vars, so the FrontController class expects something like: $_GET = [ 'a' => 'Forum', 'b' => 'Post' 'c' => '123' ]; Let's make sure our FrontController is doing everything right with a battery of tests: $aControllerTests = [ ['Forum','Post','123'], ['Blog','Post','123'], ['Article','acb'], ['Cart','Product','723'], ['Cart','Category','Jeans'] ]; $aTestResults = []; foreach($aControllerTests as $TestID => $GetVars){ $oSandbox = new SPLSandBox(); $oSandbox->MockGlobal('$_GET',$GetVars); $oController = $oSandbox->GetInstance('FrontController'); $aTestResults[$TestID] = $oController->Init(); $oSandbox->Destroy(); } SPLSandBox::MockGlobal() lets us set global variables (including super globals) inside the sandbox. Now, $aTestResults contains the results of each test, run with separate $_GET parameters. With this structure, you could get **every** valid URL from a database and run a unit test with custom $_GET params on your FrontController to make sure everything works.