Песочница для PHP

В одном из наших проектов существует возможность написания плагинов для расширения функционала сервиса.
Пользователи создают плагины-приложения в нашем интерфейсе и описывают их логику на PHP.
Необходимо было ограничить возможности PHP, чтобы никто нам случайно не нашкодил.
Существует некоторое количество инструментов для исполнения кода PHP в защищенной среде: выполнение в отдельном процессе, сохранение кода в файле и вызов через cli с урезанными возможностями или использование специализированных расширений для PHP.
В силу специфики сервиса и приложений а так же для возможности использования песочницы на всех ОС (процессы и расширения для sandbox не работают в Windows) с базовыми настройками PHP был написан небольшой класс: Ext_Sandbox_PHPValidator.

Небольшое описание класса

Внутри всего две функции:
  • static function php_syntax_error($code, $tokens = null)
  • static function validatePHPCode($source, $functions = array(), $enable = true)

php_syntax_error

Функция проверяет, правилен ли синтаксис PHP кода (не пропущены ли скобки и т.д.)
$code — php код (без <?php )
$tokens — необязательный параметр, вы можете передать его если вы уже распарсили код на токены (распарсить можно используя функцию token_get_all).
Функция возвращает ошибку в формате: array( Error Mesage, Error Line # )
Если ошибки нет — функция возвратит false.

validatePHPCode

Функция проверяет php-код и возвращает результат проверки (true или false).
$source — php-код без <?php в начале
$functions — разрешенные/запрещенные функции
$enable — boolean, если true, то $functions будут содержать список разрешенных функций, если false — список запрещенных функций.

Пример:

<?php

require 'PHPValidator.php';

$code = <<<PHP
\$b = 1;
\$c = 2;
\$a = \$b + \$c;
echo \$a;

class test {
    public function __construct() {
        echo 'construct';
    }
    public function foo(\$num) {
        var_dump(\$num);
    }
}

\$test = new test();
\$test->foo(\$a);
PHP;

// validate the code
$validator = new Ext_Sandbox_PHPValidator();

try
{
    // we enable only one function - echo, all others will throw error
    $validator->validatePHPCode( $code, array('echo'), true);
    $status = 'passed';
}
catch(Exception $ex)
{
    $status = $ex->getMessage();
}

echo 'Status of validation is: ' . $status;


Попробовать онлайн: http://ideone.com/e1qx28

Класс находится на GitHub.

Комментарии 18

    +3
    странно что у вас функции в одном классе имеют разный стиль именования
      0
      Да, php_syntax_error была честно украдена с php.net и немного исправлена/подправлена.
      validatePHPCode — уже потуги моего сознания.
      Согласен, что стиль разный и надо бы все унифицировать.
      0
      Имеется еще вот такое решение github.com/fieryprophet/php-sandbox
        0
        Да, это решение выглядит намного полнее. Хотя на первый взгляд и намного более громоздкое.
        С другой стороны, проверка кода идет с использованием статического анализатора, что теоретически может не полностью совпадать с поведением PHP ( к примеру анализатор без проблем анализирует неймспейсы, тогда как PHP в текущей версии может их не поддерживать).
        +2
        Символично, что статья написана в песочнице.
          0
          А разве runkit под виндой не работает? Вроде подробные инструкции для сборки php_runkit.dll есть.
            0
            Работает, но нужны лишние движения.
            Когда в команде удаленно работает несколько разработчиков (ну разработчики еще ладно), 2 дизайнера и у всех разные операционки, то не очень хочется всех их консультировать как заставить проект работать.
            С другой стороны, конечно, это не относится к сути проблемы. Runkit делает работу лучше. Но нам на данном этапе было проще использовать вышеприведенный класс. Свои базовые задачи он решает без проблем.
            0
            Можно же легко обойти ограничения вызоыва функций через call_user_func или даже через хитрый eval.
            Хотя ничего не стоит добавить эти функции в список запрещенных изначально.
            Нужно об этом не забывать.
              0
              По умолчанию все функции запрещены.
              eval и call_user_func могуть быть вызваны только если они явно разрешены.
              Все зависит от режима: можно разрешить по умолчанию все функции и запретить только определенные, а можно запретить все и разрешить только минимальный набор. Все на ваш вкус.
              0
              include и require пропускаются специально?
                0
                Нет, нужно добавить их обработку. Спасибо
                  0
                  еще из возникших непоняток.

                  1. пропускает вызов конструктора без параметров и скобок и дальнейшие вызовы методов созданного инстанса
                  class test {
                      public function method($a) {
                  		echo $a;
                  	}
                  }
                  $test = new test;
                  $test->method('in method');
                  
                  


                  2. пропускает статические вызовы через
                  class test {
                      public static function stat() {
                  		echo 'static';
                  	}
                  }
                  $class = 'test';
                  $class::stat();


                  В совокупности с наличием ООПных системных интерфейсов типа SplFileInfo указанные выше пункты кажутся потенциально опасными, хотя сходу что-то плохое не придумывается.
                    0
                    Вызов конструктора без параметров это нормально для PHP. В любом случае проверка синтаксиса делается на стороне PHP, я никак этим не занимаюсь.
                    По поводу вызова методов созданного класса — это сделано специально. Пользователь может насоздавать сколько угодно внутренних классов и функций. Главное, чтобы он за описанные рамки песочницы не выходил.
                    SplFileInfo — пользователь не сможет вызвать его, если не разрешим ему это явно.
                      0
                      А унаследоваться от него?
                        0
                        Да, унаследоваться и работать с ним можно… недочет.
                          0
                          Ага, клевая идея.
                          вот это пропускает:
                          class Pwd extends SplFileObject
                          {
                              public function __construct()
                              {
                                  $p = 'SplFileObject';
                                  $p::__construct('/etc/passwd');
                              }
                          }
                          
                          $pwd = new Pwd;
                          $str = $pwd->fgets();
                          echo $str;
                          

                          +1
                          Под пунктом 1 имелось ввиду, что при одинаковых настройках — разрешено только echo — код
                          class test {
                              public function method($a) 
                              {
                                  echo $a;
                              }
                          }
                          $test = new test();
                          $test->method('in method');
                          

                          не пропускается, а по сути аналогичный код без скобок — пропускакется.
                          class test {
                              public function method($a) 
                              {
                                   echo $a;
                              }
                          }
                          $test = new test;
                          $test->method('in method');
                          

                          и это как-то странно на мой взгляд.
                            0
                            Пропускается и со скобками и без скобок: ideone.com/e1qx28

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое