On Tue, Aug 6, 2024, at 10:41, Nick Lockheart wrote: > > 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. >
Hey Nick, This looks quite valuable, and I assume auto loading would work just like normal? Register an autoloader that will eventually require the file and call this function? It would be nice to provide a simplified api as well, maybe “CopyCurrentEnvironment()” or something? In most cases, it is easier/faster to find things to remove vs. adding everything on every plugin/request every time. In saying that, it would be great if there was an api for “sharing” a base-sandbox pool via shm (or similar to a pool) so that the base vm doesn’t need to be recreated potentially hundreds of times per request. — Rob