要求$输入是不安全的。 '.PHP'。然后开始上课。如何使其安全,无需使用可以被提及的类的白名单。
Ex 1.(错误代码)。
<?php
$input = $_GET['controller'];
require $input . '.php';
new $input;
?>
答案 0 :(得分:13)
<强>声明强>
我首先要说的是,在您的系统中定义静态路由是安全的,但这个答案,即使我已经努力减轻安全问题,也应该在信任其操作之前进行全面测试和理解。
基础知识
首先,使用从the manual获取的正则表达式确保控制器包含有效的变量名称;这清除了明显错误的条目:
$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()
来键入check。
自动加载
在这种情况下,您可以使用自动加载器代替使用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();
}
}
所有这些代码都假定您有适当的错误处理代码;有关实施建议,您可以查看this answer。
答案 1 :(得分:3)
一些建议:
尽可能严格地使用过滤器,例如
/* 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));
检查文件是否存在Why not file_exists? see desc
/* 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;
答案 2 :(得分:1)
大多数用户使用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类则不区分大小写。所以,如果有人进入controller = HOME,则包含将失败。
您可以通过使用小写类前缀来生成所有类似'homeController.php'的文件来防止此问题。然后,您可以使用$filename = strtolower($input).'Controller.php';
答案 3 :(得分:0)
考虑使用spl_autoload_register()
。这样可以省去验证文件/类等的大量工作。
<?php
function autoloadClasses($class) {
if (file_exists('core/'.$class.'.php')) {
include 'core/'.$class . '.php';
}
}
spl_autoload_register('autoloadClasses');
?>
然后在core-folder中保存文件名dart.php
(文件名和类名必须相同)
然后创建对象时:
new dart();
该文件将在需要时包含在内。
有关此内容的更多信息: http://php.net/manual/en/function.spl-autoload-register.php
答案 4 :(得分:0)
如果您的类/文件很少,您可以在存储类的文件夹中获取所有php文件,并检查您要包含/要求的类是否是其中之一。
这样的事情:
$classDir = '/path/to/classes';
$classList = glob($classDir.'/*.php');
$classAbsolutePath = $classDir.'/'.$_GET['class'];
if (in_array($classAbsolutePath, $classList)) {
require $classAbsolutePath;
}
如果您有子目录,则必须根据该目录修改此代码。顺便说一句,这不是关于性能的最佳解决方案,特别是如果你有很多文件和很多子目录。另外,in_array()
效率不高,所以如果你有大数组,你应该避免它。
在我看来,做这样的事情的最好方法是拥有白名单。您可以通过代码自动生成它。每次重建或部署项目时,都可以重新生成列表,以便始终拥有有效的列表。
答案 5 :(得分:0)
我建议你在允许的文件中引入特殊标记。然后在包含文件之前,将其作为纯文本阅读并查找标记。只有标签存在时,才包含它。标记可以在允许文件开头的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(...);
}
答案 6 :(得分:0)
首先添加此功能。
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
是配置文件中定义的密码,用作所有类文件的后缀。因此,只会加载带有标记后缀的类文件。
答案 7 :(得分:0)
就安全性而言,从输入接受资源的标识符没有任何问题,无论是图像还是某些代码。但是,如果预期会有某种授权,那就不可避免地要避免某种授权(显然,获得授权是一种悖论,但却没有授权)。因此,如果您坚持不使用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();
}
假设已经设置了一些自动加载器。
答案 8 :(得分:-1)
在什么情况下,您将允许用户通过查询字符串参数实例化控制器,但不知道他们实际尝试实例化的内容?听起来像灾难的食谱。
这样说,我只是将输入限制为字母(假设您的类名为 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');
}