根据用户输入创建类

php
2022-08-30 21:16:03

要求$input是不安全的。“.php”。然后插入一个类。我如何使它安全,而不需要使用可以识别的类的白名单。

例 1.(代码错误)。

<?php

$input = $_GET['controller'];

require $input . '.php';

new $input;

?>

答案 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 {}

顺便说一句,为了避免名称冲突,您可以在单独的命名空间中定义它们。

这就是你如何强制实施这样的层次结构:

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));
    
  • 检查文件是否存在 为什么不file_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;
    

推荐