Подтвердить что ты не робот

Создание класса на основе ввода пользователем

Небезопасно требовать $input. '.php'. Затем создать класс. Как я могу сделать это безопасным, без необходимости использовать белый список классов, которые могут быть проинформированы.

Пример 1. (неправильный код).

<?php

$input = $_GET['controller'];

require $input . '.php';

new $input;

?>
4b9b3361

Ответ 1

Отказ

Я должен начать с того, что определение статических маршрутов в вашей системе является безопасным по дизайну, тогда как этот ответ, даже если я прилагал усилия для смягчения проблем безопасности, должен быть тщательно протестирован и понят, прежде чем доверять его работе.

Основы

Сначала убедитесь, что контроллер содержит допустимое имя переменной, используя регулярное выражение, взятое из руководство; это уничтожает очевидные ошибочные записи:

$controller = filter_input(INPUT_GET, FILTER_VALIDATE_REGEXP, [
    'options' => [
        'regexp' => '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/',
        'flags' => FILTER_NULL_ON_FAILURE,
    ]
]);

if ($controller !== null) {
    // load and use controller
    require_once("$controller.php");
    $c = new $controller();
}

Обеспечение иерархии

Это хорошо работает, но что, если кто-то пытается вместо этого загрузить внутренний класс? Это может сильно испортить приложение.

Вы можете ввести абстрактный базовый класс или интерфейс, которые все ваши контроллеры должны расширять или реализовывать:

abstract class Controller {}

// e.g. controller for '?controller=admin'
class Admin extends Controller {}

Btw, чтобы избежать конфликтов имен, вы можете определить их внутри отдельного пространства имен.

И именно так вы будете применять такую ​​иерархию:

if ($controller !== null) {
    // load and use controller
    require_once("$controller.php");
    if (is_subclass_of($controller, 'Controller')) {
        $c = new $controller();
    }
}

Я использую is_subclass_of(), чтобы ввести проверку перед созданием класса.

Автоматическая загрузка

Вместо использования require_once() в этом случае вместо этого вы можете использовать автоматический загрузчик:

// register our custom auto loader
spl_autoload_register(function($class) {
    $file = "$class.php"; // admin -> admin.class.php
    if (file_exists($file)) {
        require_once $file; // this can be changed
    }
});

Это также место, где вы можете нормализовать имя класса, чтобы оно лучше отображалось в имени файла, а также принудительное использование пользовательского пространства имен, например. "App\\$class.php".

Это уменьшает код на одну строку, но делает загрузку более гибкой:

if ($controller !== null) {
    // check hierarchy (this will attempt auto loading)
    if (class_exists($controller) && is_subclass_of($controller, 'Controller')) {
        $c = new $controller();
    }
}

Весь этот код предполагает, что у вас есть правильный код обработки ошибок; для рекомендаций по реализации вы можете посмотреть этот ответ.

Ответ 2

Несколько предложений:

  • Поместите классы контроллера в свою собственную выделенную папку, содержащую ТОЛЬКО классы контроллера
  • Сделайте свой фильтр как можно более строгим, например.

    /* is $_GET['controller'] set? */
    if (!isset($_GET['controller'])) {
        // load error or default controller???
    }
    
    $loadController = $_GET['controller'];
    
    /* replace any characters NOT matching a-z or _ (whitelist approach), case insensitive */
    $loadController = preg_replace('/[^a-z_]+/i', '', $loadController);
    
    /* verify var is not empty now :) */
    if (!$loadController) {
        // load error or default controller???
    }
    
    /* if your classes are named in a certain fashion, eg. "Classname", format the incoming text to match ** NEVER TRUST USER INPUT ** */
    $loadController = ucfirst(strtolower($loadController));
    
  • Проверьте, существует ли файл Почему не файл_exists? см. ниже

    /* avoiding using file_exists as it also matches folders... */
    if (!is_file($myControllerClassesPath.$loadController.'.php')) {
        // load error or default controller???
    }
    
  • Затем потребуется файл и убедитесь, что сам класс существует

    require($myControllerClassesPath.$loadController.'.php');
    
    /* of course, this assumes filename === classname, adjust accordingly */
    if (!class_exists($loadController)) {
        // load error or default controller???
    }
    
  • Тогда, конечно, новый экземпляр X

    new $loadController;
    

Ответ 3

Большинство пользователей используют вариант auto_load вместо включения, чтобы сделать его более безопасным. Но приведенные примеры не могут упомянуть, что способ, которым они его используют, auto_load - это просто фантазия. Вместо того, чтобы включать файл вручную, а затем вызывать класс, файл включается автоматически. Это не дает никаких преимуществ для обеспечения безопасности, поскольку по-прежнему можно вызвать любой доступный класс.

В моем варианте с использованием include вместо require, а затем поймать ошибку является наилучшей практикой и проще всего реализовать. Чтобы обеспечить безопасность, вы должны добавить дополнительную часть имен файлов, которые вы хотите включить. EG: "Контроллер". Теперь, если у вас есть класс под названием Home, вы вызываете файл homeController.php. Таким образом, мы можем требовать только файлы, заканчивающиеся на "Controller.php".

В качестве дополнительной меры предосторожности я добавил basename() на вход, чтобы предотвратить доступ к сети в системах Windows

<?php
//EG GET ?controller=home
$input = isset($_GET['controller']) ? $_GET['controller'] : "";
if (empty($input))
  die('No controller');

$input = basename($input);
$filename = $input.'Controller.php';

//since only valid files can be included, you dont need to check for valid chars or anything. Just make sure that only your controller files end with 'Controller.php'
//use the @ to hide the warning when the file does not exist
if ((@include $filename) !== 1)
  die('Unknown controller');

//no error, so we included a valid controller and now we can call it.
$controller = new $input();
?>

Имейте в виду, что если вы запускаете ни один сервер Windows, ваши имена файлов чувствительны к регистру, в то время как ваши классы PHP не являются. поэтому, если кто-то войдет в контроллер = HOME, то включение будет терпеть неудачу.

Вы можете предотвратить эту проблему, выполнив все файлы типа homeController.php с префиксом нижнего регистра. Затем вы можете использовать $filename = strtolower($input).'Controller.php';

Ответ 4

Рассмотрим использование spl_autoload_register(). Это поможет вам сэкономить много усилий при проверке файлов/классов и т.д.

<?php
function autoloadClasses($class) {
    if (file_exists('core/'.$class.'.php')) {
        include 'core/'.$class . '.php';
    }
}
spl_autoload_register('autoloadClasses');

?>

Затем сохраните имя файла dart.php в основной папке (имя файла и имя класса должны быть одинаковыми)

Когда вы затем создаете объект: new dart(); файл будет включен, если необходимо.

Дополнительная информация: http://php.net/manual/en/function.spl-autoload-register.php

Ответ 5

Если у вас мало классов/файлов, вы можете получить все php файлы в папке, где хранятся классы, и проверить, является ли класс, который вы хотите включить/требовать, одним из них.

Так что-то вроде этого:

$classDir = '/path/to/classes';
$classList = glob($classDir.'/*.php');
$classAbsolutePath = $classDir.'/'.$_GET['class'];

if (in_array($classAbsolutePath, $classList)) {
    require $classAbsolutePath;
}

Если у вас есть подкаталоги, вам необходимо изменить этот код в соответствии с этим. Кстати, это не лучшее решение, касающееся выступлений, особенно если у вас много файлов и много подкаталогов. Кроме того, in_array() не очень эффективен, поэтому вам следует избегать его, если у вас большие массивы.

На мой взгляд, лучший способ сделать что-то вроде этого - иметь белый список. Вы можете автоматически генерировать его с помощью кода. Каждый раз, когда вы перестраиваете или развертываете свой проект, вы можете восстановить список, чтобы у вас всегда был действующий.

Ответ 6

Я бы предложил вам ввести специальный тег в разрешенные файлы. Затем перед включением файла прочитайте его как обычный текст и найдите тег. Только если тег присутствует, включите его. Тег может быть внутри комментария PHP в начале разрешенных файлов.

$class = $_GET['class'];
if (preg_match('/^[a-zA-Z]+$/', $class))
{
    $file = $class.".php";
    if (is_file($file)) {
    {
        $content = file_get_contents($file);
        if (strpos($content, "THECLASSMAGIC") !== false)
        {
            require($file);
        }
    }
    else
    {
        die(...);
    }
}
else
{
    die(...);
}

Ответ 7

Сначала добавьте эту функцию.

function __autoload ( $class ) {
     $path = "../path/to/class/dir/" . $class . TOKEN . ".php";
     if ( file_exists ($path) ) {
          require_once ( $path );
     } else {
          // class not found.
     }
}

Затем просто класс доступа,

$class = new input();

Он будет проверять, существует ли файл "../path/to/class/dir/input_secretToken.php" и включать его автоматически.

Здесь TOKEN - секретное слово, определенное в файле конфигурации и используемое как суффикс для всех файлов классов. Таким образом, будет загружен только файл класса с суффиксом токена.

Ответ 8

Вы можете использовать spl_autoload_register()

function my_autoload($className) {
    $phpFolders = array('models', 'controllers');
    foreach($phpFolders as $folder) {
        if(file_exists($folder . '/' . $className . '.php')) {
            require_once $folder . '/' . $className . '.php';
        }
    }
}
spl_autoload_register('my_autoload');

$input = $_GET['controller'];
new $input();

Ответ 9

Что касается безопасности, нет ничего плохого в принятии идентификатора ресурсов от ввода, будь то изображение или какой-то код. Но неизбежно избегать какой-то авторизации, если ее можно ожидать (очевидно, это парадокс, чтобы иметь разрешение, но не иметь его). Поэтому, если вы настаиваете на отсутствии ACL (или "белого списка", как вы его называете), я должен сказать, что вы не хотите.

С другой стороны, если вы можете договориться с ACL, то остальное просто. Все, что вам нужно сделать, это увидеть ваши контроллеры в качестве ресурсов и сгруппировать своих пользователей в роли (эта последняя часть является необязательной). Затем укажите, какая роль или пользователь может получить доступ к этому контроллеру. Здесь, как это делается, используя Zend Framework.

$acl = new Zend_Acl();

$acl->addRole(new Zend_Acl_Role('guest'))
    ->addRole(new Zend_Acl_Role('member'))
    ->addRole(new Zend_Acl_Role('admin'));

$parents = array('guest', 'member', 'admin');
$acl->addRole(new Zend_Acl_Role('someUser'), $parents);

$acl->add(new Zend_Acl_Resource('someController'));

$acl->deny('guest', 'someController');
$acl->allow('member', 'someController');

Затем, когда некоторые запросы будут получены, вы можете задать вопрос о его авторизации просто так:

if ($acl->isAllowed('currentUser', $_GET['controller'])) {
    $ctrlClass = $_GET['controller'];
    $controller = new $ctrlClass();
}

Предположим, что уже установлен один автозагрузчик.

Ответ 10

В каком экземпляре вы собираетесь разрешить пользователю создавать экземпляр контроллера через шаблон строки запроса, но не иметь представления о том, что они на самом деле пытаются создать? Звучит как рецепт катастрофы.

Говоря, что Id ограничивает ввод только письмами (предполагается, что ваши классы называются MyClass.php, MyOtherClass.php и т.д.) и заблокированы для определенного каталога.

<?php

$className = $_GET['file'];
$dir = '/path/to/classes/';
$file = $dir . $className . '.php';

if (preg_match('/^[a-zA-Z]+$/', $className) && is_file($file)) {
    require($file);
    $class = new $className;
}
else {
    die('Class not found');
}