Skip to content

Instantly share code, notes, and snippets.

@tryvin
Created March 7, 2016 14:02
Show Gist options
  • Save tryvin/a9f4622601d19a425267 to your computer and use it in GitHub Desktop.
Save tryvin/a9f4622601d19a425267 to your computer and use it in GitHub Desktop.
A basic class to use when loading plugins in a controlled environment within PHP, this class uses runkit to redefine the functions, adding some random prefix, and controlling the access to these functions, like file parameters base dir and such
<?php
class sandboxPluginLoader {
private $fileSystemFunctionsMap = array(
'realpath' => 0,
'chgrp' => 0, 'chmod' => 0, 'chown' => 0,
'unlink' => 0, 'copy' => array(0, 1), 'file' => 0,
'file_exists' => 0, 'file_get_contents' => 0, 'file_put_contents' => 0,
'flock' => 0, 'fopen' => 0, 'glob' => 0,
'link' => array(0, 1), 'readfile' => 0, 'readlink' => 0,
'rename' => array(0, 1), 'rmdir' => 0,
'symlink' => array(0, 1)
);
private $forbiddenFunctions = array(
'var_dump', 'print_r', 'get_declared_classes', 'phpinfo',
'exec', 'shell_exec', 'passthru', 'system',
'class_exists', 'get_declared_interfaces', 'get_object_vars'
);
private $functionPrefix = '';
private $pluginDirectoryName = '';
static $sandboxInstance = false;
static $isRunningSandbox = false;
static function instance($pluginDirectoryName = '') {
if ( sandboxPluginLoader::$sandboxInstance === false )
sandboxPluginLoader::$sandboxInstance = new sandboxPluginLoader($pluginDirectoryName);
return sandboxPluginLoader::$sandboxInstance;
}
public function __construct($pluginDirectoryName = '') {
if ( ! function_exists('runkit_function_rename') )
throw new Exception("No Runkit found");
$this->functionPrefix = "_" . md5(mt_rand());
$this->pluginDirectoryName = $pluginDirectoryName;
}
public function startSandbox() {
$this->redefineFileSystem();
$this->redefineForbidden();
sandboxPluginLoader::$isRunningSandbox = true;
}
public function isRunningSandbox() {
return sandboxPluginLoader::$isRunningSandbox;
}
private function redefineForbidden() {
foreach($this->forbiddenFunctions as $functionName) {
runkit_function_rename(
$functionName,
sprintf('%s_%s',
$this->functionPrefix,
$functionName
)
);
$functionCode = sprintf(
'$baseDir = "%1$s";
$callStack = debug_backtrace();
$filePath = %2$s_realpath(dirname($callStack[0]["file"])) . DIRECTORY_SEPARATOR;
if ( substr($filePath, 0, strlen($baseDir)) == $baseDir ) {
return false;
}
return call_user_func_array(
"%2$s_%3$s",
func_get_args()
);
',
$this->pluginDirectoryName,
$this->functionPrefix,
$functionName
);
runkit_function_add($functionName, '', $functionCode);
}
}
private function redefineFileSystem() {
foreach($this->fileSystemFunctionsMap as $functionName => $fileParam) {
runkit_function_rename(
$functionName,
sprintf('%s_%s',
$this->functionPrefix,
$functionName
)
);
$functionCode = sprintf(
'$baseDir = "%1$s";
$fileArg = %2$s;
$functionArguments = func_get_args();
$callStack = debug_backtrace();
$filePath = %3$s_realpath(dirname($callStack[0]["file"])) . DIRECTORY_SEPARATOR;
if ( substr($filePath, 0, strlen($baseDir)) == $baseDir ) {
if ( is_array($fileArg) ) {
foreach($fileArg as $fIndex) {
$filePath = %3$s_realpath(dirname($functionArguments[$fIndex])) . DIRECTORY_SEPARATOR;
if ( substr($filePath, 0, strlen($baseDir)) != $baseDir )
return false;
}
}
else if ( count($functionArguments) > $fileArg ) {
$filePath = %3$s_realpath(dirname($functionArguments[$fileArg])) . DIRECTORY_SEPARATOR;
if ( substr($filePath, 0, strlen($baseDir)) != $baseDir )
return false;
}
}
set_error_handler(function($errno, $errstr, $errfile, $errline, $errcontext) {
if ( 0 == error_reporting() )
return false;
$errstr = str_replace("%3$s_%4$s", "%4$s", $errstr);
restore_error_handler();
trigger_error($errstr, E_USER_WARNING);
});
$returnedFunction = call_user_func_array(
"%3$s_%4$s",
func_get_args()
);
restore_error_handler();
return $returnedFunction;
',
$this->pluginDirectoryName,
(is_array($fileParam) ?
sprintf("array(%s)", implode(",", $fileParam)) : $fileParam),
$this->functionPrefix,
$functionName
);
runkit_function_add($functionName, '', $functionCode);
}
}
}
/*EXAMPLE USAGE: instance it withing the plugin directory as a param,
and start the sandbox, after started, it goes till your script finish
*/
sandboxPluginLoader::instance(realpath('plugins/testplugin') . DIRECTORY_SEPARATOR)->startSandbox();
//NOW: includes the plugin
require_once('plugins/testplugin/controller.php');
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment