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