下面我们将回顾常见的安全原则,并说明在使用 Yii 开发应用程序时如何避免威胁。大多数这些原则并不仅仅适用于 Yii,而是适用于网站或软件开发的通用原则,因此您还会发现有关这些原则背后一般理念的进一步阅读链接。
无论开发哪个应用程序,在安全方面有两个主要原则
过滤输入意味着绝不应将输入视为安全,您应该始终检查您获得的值是否实际上是允许的值。例如,如果我们知道排序可以按三个字段 title
、created_at
和 status
进行,并且该字段可以通过用户输入提供,那么最好在接收该值的地方检查我们获得的值。在基本 PHP 中,这将类似于以下内容
$sortBy = $_GET['sort'];
if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
throw new Exception('Invalid sort value.');
}
在 Yii 中,您很可能将使用 表单验证 来进行类似的检查。
有关该主题的进一步阅读
转义输出意味着,根据我们使用数据的上下文,它应该被转义,例如,在 HTML 上下文中,您应该转义 <
、>
和类似的特殊字符。在 JavaScript 或 SQL 上下文中,它将是不同的字符集。由于手动转义所有内容容易出错,Yii 提供了各种工具来针对不同的上下文执行转义。
有关该主题的进一步阅读
当查询文本通过连接未转义的字符串形成时,就会发生 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 或跨站脚本攻击发生在将 HTML 输出到浏览器时,输出没有正确转义。例如,如果用户可以输入自己的姓名,并且他输入了 <script>alert('Hello!');</script>
而不是 Alexander
,那么每个输出用户姓名而没有转义的页面都会执行 JavaScript alert('Hello!');
,导致浏览器弹出警示框。根据网站的不同,这种脚本除了无害的警示之外,还可以使用你的姓名发送消息,甚至进行银行交易。
在 Yii 中,避免 XSS 非常容易。通常有两种情况:
如果你只需要纯文本,转义就和下面一样简单:
<?= \yii\helpers\Html::encode($username) ?>
如果应该是 HTML,我们可以借助 HtmlPurifier。
<?= \yii\helpers\HtmlPurifier::process($description) ?>
请注意,HtmlPurifier 的处理非常耗费资源,因此请考虑添加缓存。
有关该主题的进一步阅读
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,你应该始终:
有时你可能需要根据控制器和/或操作禁用 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.php
的 web
目录。在共享托管环境中,可能无法实现这一点,最终导致所有代码、配置和日志都位于服务器 Web 根目录中。
如果是这种情况,请不要忘记拒绝访问除 web
之外的所有内容。如果无法做到这一点,请考虑将你的应用程序托管在其他地方。
在调试模式下,Yii 会显示非常详细的错误信息,这对开发来说非常有用。问题是,这些详细的错误信息对攻击者也很有用,因为它们可能会泄露数据库结构、配置值和代码部分。永远不要在你的 index.php
中将 YII_DEBUG
设置为 true
来运行生产应用程序。
你永远不应该在生产环境中启用 Gii 或调试工具栏。它可以用来获取有关数据库结构、代码的信息,以及简单地用 Gii 生成的代码来重写代码。
调试工具栏应在生产环境中避免使用,除非绝对必要。它会暴露所有可能的应用程序和配置详细信息。如果你绝对需要它,请仔细检查访问是否只限制在你的 IP 地址上。
有关该主题的进一步阅读
Yii 提供了依赖于 cookie 和/或 PHP 会话的功能。如果你的连接被泄露,这些功能可能会受到攻击。如果应用程序使用 TLS 安全连接(通常称为 SSL),则可以降低风险。
有关如何配置 Web 服务器的说明,请参阅你的 Web 服务器文档。你也可以查看 H5BP 项目提供的示例配置。
注意:配置 TLS 后,建议仅通过 TLS 发送(会话)cookie。这可以通过为会话和/或 cookie 设置
secure
标志来实现。有关更多信息,请参阅 会话和 Cookie 安全标志。
本节的目的是突出在创建用于提供基于 Yii 的网站的服务器配置时需要考虑的风险。除了这里提到的内容之外,可能还需要考虑其他与安全相关的配置选项,因此不要认为本节是完整的。
Host
头部攻击 ¶诸如 yii\web\UrlManager 和 yii\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 证书验证问题,存在一个普遍的误解,例如:
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。
openssl.cafile="/path/to/cacert.pem" curl.cainfo="/path/to/cacert.pem".
请注意,cacert.pem
文件应该保持最新。
发现错别字,或者你认为此页面需要改进?
在 github 上编辑它 !
注册 或 登录 以发表评论。