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.

Reply via email to