4 关注者

安全最佳实践

下面我们将回顾常见的安全原则,并说明在使用 Yii 开发应用程序时如何避免威胁。大多数这些原则并不仅仅适用于 Yii,而是适用于网站或软件开发的通用原则,因此您还会发现有关这些原则背后一般理念的进一步阅读链接。

基本原则

无论开发哪个应用程序,在安全方面有两个主要原则

  1. 过滤输入。
  2. 转义输出。

过滤输入

过滤输入意味着绝不应将输入视为安全,您应该始终检查您获得的值是否实际上是允许的值。例如,如果我们知道排序可以按三个字段 titlecreated_atstatus 进行,并且该字段可以通过用户输入提供,那么最好在接收该值的地方检查我们获得的值。在基本 PHP 中,这将类似于以下内容

$sortBy = $_GET['sort'];
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
	throw new Exception('Invalid sort value.');
}

在 Yii 中,您很可能将使用 表单验证 来进行类似的检查。

有关该主题的进一步阅读

转义输出

转义输出意味着,根据我们使用数据的上下文,它应该被转义,例如,在 HTML 上下文中,您应该转义 <> 和类似的特殊字符。在 JavaScript 或 SQL 上下文中,它将是不同的字符集。由于手动转义所有内容容易出错,Yii 提供了各种工具来针对不同的上下文执行转义。

有关该主题的进一步阅读

避免 SQL 注入

当查询文本通过连接未转义的字符串形成时,就会发生 SQL 注入,例如以下内容

$username = $_GET['username'];
$sql = "SELECT * FROM user WHERE username = '$username'";

攻击者不会提供正确的用户名,而是会提供类似 '; DROP TABLE user; -- 的内容。最终生成的 SQL 语句将如下所示:

SELECT * FROM user WHERE username = ''; DROP TABLE user; --'

这是一个有效的查询,它将搜索用户名为空的用户,然后删除 user 表,这很可能会导致网站崩溃和数据丢失(你已经设置了定期备份,对吧?)。

在 Yii 中,大多数数据库查询都是通过 Active Record 完成的,它在内部正确地使用 PDO 预处理语句。对于预处理语句,无法像上面演示的那样操作查询。

尽管如此,有时你可能需要使用 原始查询查询构建器。在这种情况下,你应该使用安全的方式传递数据。如果数据用于列值,建议使用预处理语句。

// query builder
$userIDs = (new Query())
    ->select('id')
    ->from('user')
    ->where('status=:status', [':status' => $status])
    ->all();

// DAO
$userIDs = $connection
    ->createCommand('SELECT id FROM user where status=:status')
    ->bindValues([':status' => $status])
    ->queryColumn();

如果数据用于指定列名或表名,最佳做法是只允许预定义的值集。

function actionList($orderBy = null)
{
    if (!in_array($orderBy, ['name', 'status'])) {
        throw new BadRequestHttpException('Only name and status are allowed to order by.')
    }
    
    // ...
}

如果无法做到这一点,则应转义表名和列名。Yii 有专门的语法用于这种转义,可以使所有支持的数据库以相同的方式进行转义。

$sql = "SELECT COUNT([[$column]]) FROM {{table}}";
$rowCount = $connection->createCommand($sql)->queryScalar();

可以在 转义表名和列名 中找到有关语法的详细信息。

有关该主题的进一步阅读

避免 XSS

XSS 或跨站脚本攻击发生在将 HTML 输出到浏览器时,输出没有正确转义。例如,如果用户可以输入自己的姓名,并且他输入了 <script>alert('Hello!');</script> 而不是 Alexander,那么每个输出用户姓名而没有转义的页面都会执行 JavaScript alert('Hello!');,导致浏览器弹出警示框。根据网站的不同,这种脚本除了无害的警示之外,还可以使用你的姓名发送消息,甚至进行银行交易。

在 Yii 中,避免 XSS 非常容易。通常有两种情况:

  1. 你想将数据作为纯文本输出。
  2. 你想将数据作为 HTML 输出。

如果你只需要纯文本,转义就和下面一样简单:

<?= \yii\helpers\Html::encode($username) ?>

如果应该是 HTML,我们可以借助 HtmlPurifier。

<?= \yii\helpers\HtmlPurifier::process($description) ?>

请注意,HtmlPurifier 的处理非常耗费资源,因此请考虑添加缓存。

有关该主题的进一步阅读

避免 CSRF

CSRF 是跨站请求伪造的缩写。其原理是,许多应用程序假设来自用户浏览器的请求是由用户自己发出的。这个假设可能是错误的。

例如,网站 an.example.com 有一个 /logout URL,当使用简单的 GET 请求访问时,会将用户注销。只要是由用户自己发出的请求,一切都正常,但有一天,黑客在用户经常访问的论坛上发布了 <img src="https://an.example.com/logout">。浏览器不会区分请求图像和请求页面,因此当用户打开包含此类修改后的 <img> 标签的页面时,浏览器会向该 URL 发送 GET 请求,用户将从 an.example.com 注销。

这就是 CSRF 攻击的基本原理。有人可能会说,注销用户并不是什么严重的事情,但这只是一个例子,还有很多其他事情可以通过这种方法完成,例如触发支付或更改数据。假设某个网站有一个 URL https://an.example.com/purse/transfer?to=anotherUser&amount=2000。使用 GET 请求访问它,会导致从授权用户的账户向用户 anotherUser 转账 2000 美元。我们知道,浏览器总是会发送 GET 请求来加载图像,因此我们可以修改代码,只接受该 URL 上的 POST 请求。不幸的是,这并不能拯救我们,因为攻击者可以在 <img> 标签中放入一些 JavaScript 代码,允许向该 URL 发送 POST 请求。

因此,Yii 应用了额外的机制来防止 CSRF 攻击。

为了避免 CSRF,你应该始终:

  1. 遵循 HTTP 规范,即 GET 不应该改变应用程序状态。有关更多详细信息,请参阅 RFC2616
  2. 保持 Yii CSRF 保护处于启用状态。

有时你可能需要根据控制器和/或操作禁用 CSRF 验证。可以通过设置其属性来实现:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public $enableCsrfValidation = false;

    public function actionIndex()
    {
        // CSRF validation will not be applied to this and other actions
    }

}

要禁用自定义操作的 CSRF 验证,可以执行以下操作:

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function beforeAction($action)
    {
        // ...set `$this->enableCsrfValidation` here based on some conditions...
        // call parent method that will check CSRF if such property is `true`.
        return parent::beforeAction($action);
    }
}

独立操作 中禁用 CSRF 验证必须在 init() 方法中完成。不要将此代码放在 beforeRun() 方法中,因为它不会生效。

<?php

namespace app\components;

use yii\base\Action;

class ContactAction extends Action
{
    public function init()
    {
        parent::init();
        $this->controller->enableCsrfValidation = false;
    }

    public function run()
    {
          $model = new ContactForm();
          $request = Yii::$app->request;
          if ($request->referrer === 'yiipowered.com'
              && $model->load($request->post())
              && $model->validate()
          ) {
              $model->sendEmail();
          }
    }
}

警告:禁用 CSRF 将允许任何网站向你的网站发送 POST 请求。在这种情况下,重要的是实现额外的验证,例如检查 IP 地址或秘密令牌。

注意:从 2.0.21 版开始,Yii 支持 sameSite cookie 设置(需要 PHP 版本 7.3.0 或更高版本)。设置 sameSite cookie 设置不会使上述内容过时,因为并非所有浏览器都支持此设置。有关更多信息,请参阅 会话和 Cookie sameSite 选项

有关该主题的进一步阅读

避免任意对象实例化

Yii 配置 是关联数组,框架通过 Yii::createObject($config) 使用它们来实例化新对象。这些数组指定了要实例化的类名,重要的是要确保此类名不是来自不可信源。否则,它会导致不安全的反射,这种漏洞允许通过利用特定类的加载来执行恶意代码。此外,当你需要动态地向从框架类派生的对象(例如基本 Component 类)添加键时,必须使用白名单方法验证这些动态属性。采取此预防措施是必要的,因为框架可能会在 __set() 魔术方法中使用 Yii::createObject($config)

避免文件暴露

默认情况下,服务器 Web 根目录应指向包含 index.phpweb 目录。在共享托管环境中,可能无法实现这一点,最终导致所有代码、配置和日志都位于服务器 Web 根目录中。

如果是这种情况,请不要忘记拒绝访问除 web 之外的所有内容。如果无法做到这一点,请考虑将你的应用程序托管在其他地方。

避免在生产环境中使用调试信息和工具

在调试模式下,Yii 会显示非常详细的错误信息,这对开发来说非常有用。问题是,这些详细的错误信息对攻击者也很有用,因为它们可能会泄露数据库结构、配置值和代码部分。永远不要在你的 index.php 中将 YII_DEBUG 设置为 true 来运行生产应用程序。

你永远不应该在生产环境中启用 Gii 或调试工具栏。它可以用来获取有关数据库结构、代码的信息,以及简单地用 Gii 生成的代码来重写代码。

调试工具栏应在生产环境中避免使用,除非绝对必要。它会暴露所有可能的应用程序和配置详细信息。如果你绝对需要它,请仔细检查访问是否只限制在你的 IP 地址上。

有关该主题的进一步阅读

使用 TLS 安全连接

Yii 提供了依赖于 cookie 和/或 PHP 会话的功能。如果你的连接被泄露,这些功能可能会受到攻击。如果应用程序使用 TLS 安全连接(通常称为 SSL),则可以降低风险。

有关如何配置 Web 服务器的说明,请参阅你的 Web 服务器文档。你也可以查看 H5BP 项目提供的示例配置。

注意:配置 TLS 后,建议仅通过 TLS 发送(会话)cookie。这可以通过为会话和/或 cookie 设置 secure 标志来实现。有关更多信息,请参阅 会话和 Cookie 安全标志

安全服务器配置

本节的目的是突出在创建用于提供基于 Yii 的网站的服务器配置时需要考虑的风险。除了这里提到的内容之外,可能还需要考虑其他与安全相关的配置选项,因此不要认为本节是完整的。

避免 Host 头部攻击

诸如 yii\web\UrlManageryii\helpers\Url 之类的类可能会使用 当前请求的域名 来生成链接。如果 Web 服务器被配置为独立于 Host 头部值的设置来提供相同的站点,则此信息可能不可靠,并且 可能被发送 HTTP 请求的用户伪造。在这种情况下,你应该要么修复 Web 服务器配置,使其仅为指定域名提供服务,要么通过设置 request 应用程序组件的 hostInfo 属性来显式设置或过滤该值。

有关服务器配置的更多信息,请参阅你的 Web 服务器文档。

如果你没有访问服务器配置的权限,可以在应用程序级别设置 yii\filters\HostControl 过滤器,以防止此类攻击。

// Web Application configuration file
return [
    'as hostControl' => [
        'class' => 'yii\filters\HostControl',
        'allowedHosts' => [
            'example.com',
            '*.example.com',
        ],
        'fallbackHostInfo' => 'https://example.com',
    ],
    // ...
];

注意:你应该始终优先使用 Web 服务器配置来进行“主机头攻击”保护,而不是使用过滤器。仅当服务器配置设置不可用时,才应使用 yii\filters\HostControl

配置 SSL 对等验证

关于如何解决 SSL 证书验证问题,存在一个普遍的误解,例如:

cURL error 60: SSL certificate problem: unable to get local issuer certificate

stream_socket_enable_crypto(): SSL operation failed with code 1. OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed

许多来源错误地建议禁用 SSL 对等验证。这永远不应该这样做,因为它会启用中间人类型的攻击。相反,应正确配置 PHP。

  1. 下载 https://curl.haxx.se/ca/cacert.pem
  2. 在你的 php.ini 中添加以下内容: openssl.cafile="/path/to/cacert.pem" curl.cainfo="/path/to/cacert.pem".

请注意,cacert.pem 文件应该保持最新。

发现错别字,或者你认为此页面需要改进?
在 github 上编辑它 !