|
|
|
@ -1,23 +1,21 @@
|
|
|
|
|
Security best practices |
|
|
|
|
最佳安全实践 |
|
|
|
|
======================= |
|
|
|
|
|
|
|
|
|
Below we'll review common security principles and describe how to avoid threats when developing applications using Yii. |
|
|
|
|
下面,我们将会回顾常见的安全原则,并介绍在使用 Yii 开发应用程序时,如何避免潜在安全威胁。 |
|
|
|
|
|
|
|
|
|
Basic principles |
|
|
|
|
基本准则 |
|
|
|
|
---------------- |
|
|
|
|
|
|
|
|
|
There are two main principles when it comes to security no matter which application is being developed: |
|
|
|
|
无论是开发何种应用程序,我们都有两条基本的安全准则: |
|
|
|
|
|
|
|
|
|
1. Filter input. |
|
|
|
|
2. Escape output. |
|
|
|
|
1. 过滤输入 |
|
|
|
|
2. 转义输出 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Filter input |
|
|
|
|
### 过滤输入 |
|
|
|
|
|
|
|
|
|
Filter input means that input should never be considered safe and you should always check if the value you've got is |
|
|
|
|
actually among allowed ones. For example, if we know that sorting could be done by three fields `title`, `created_at` and `status` |
|
|
|
|
and the field could be supplied via user input, it's better to check the value we've got right where we're receiving it. |
|
|
|
|
In terms of basic PHP that would look like the following: |
|
|
|
|
过滤输入的意思是,用户输入不应该认为是安全的,你需要总是验证你获得的输入值是在允许范围内。比如,我们假设 sorting 只能指定为 `title`, `created_at` 和 `status` 三个值,然后,这个值是由用户输入提供的,那么,最好在我们接收参数的时候,检查一下这个值是否是指定的范围。 |
|
|
|
|
对于基本的 PHP 而言,上述做法类似如下: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$sortBy = $_GET['sort']; |
|
|
|
@ -26,41 +24,36 @@ if (!in_array($sortBy, ['title', 'created_at', 'status'])) {
|
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
In Yii, most probably you'll use [form validation](input-validation.md) to do alike checks. |
|
|
|
|
在 Yii 中,很大可能性,你会使用 [表单校验器](input-validation.md) 来执行类似的检查。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Escape output |
|
|
|
|
### 转义输出 |
|
|
|
|
|
|
|
|
|
Escape output means that depending on context where we're using data it should be escaped i.e. in context of HTML you |
|
|
|
|
should escape `<`, `>` and alike special characters. In context of JavaScript or SQL it will be different set of characters. |
|
|
|
|
Since it's error-prone to escape everything manually Yii provides various tools to perform escaping for different |
|
|
|
|
contexts. |
|
|
|
|
转义输出的意思是,根据我们使用数据的上下文环境,数据需要被转义。比如:在 HTML 上下文,你需要转义 `<`,`>` 之类的特殊字符。在 JavaScript 或者 SQL 中,也有其他的特殊含义的字符串需要被转义。 |
|
|
|
|
由于手动的给所用的输出转义容易出错,Yii 提供了大量的工具来在不同的上下文执行转义。 |
|
|
|
|
|
|
|
|
|
Avoiding SQL injections |
|
|
|
|
避免 SQL 注入 |
|
|
|
|
----------------------- |
|
|
|
|
|
|
|
|
|
SQL injection happens when query text is formed by concatenating unescaped strings such as the following: |
|
|
|
|
SQL 注入发生在查询语句是由连接未转义的字符串生成的场景,比如: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$username = $_GET['username']; |
|
|
|
|
$sql = "SELECT * FROM user WHERE username = '$username'"; |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Instead of supplying correct username attacker could give your applications something like `'; DROP TABLE user; --`. |
|
|
|
|
Resulting SQL will be the following: |
|
|
|
|
除了提供正确的用户名外,攻击者可以给你的应用程序输入类似 '; DROP TABLE user; --` 的语句。 |
|
|
|
|
这将会导致生成如下的 SQL : |
|
|
|
|
|
|
|
|
|
```sql |
|
|
|
|
SELECT * FROM user WHERE username = ''; DROP TABLE user; --' |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
This is valid query that will search for users with empty username and then will drop `user` table most probably |
|
|
|
|
resulting in broken website and data loss (you've set up regular backups, right?). |
|
|
|
|
这是一个合法的查询语句,并将会执行以空的用户名搜索用户操作,然后,删除 `user` 表。这极有可能导致网站出差,数据丢失。(你是否进行了规律的数据备份?) |
|
|
|
|
|
|
|
|
|
In Yii most of database querying happens via [Active Record](db-active-record.md) which properly uses PDO prepared |
|
|
|
|
statements internally. In case of prepared statements it's not possible to manipulate query as was demonstrated above. |
|
|
|
|
在 Yii 中,大部分的数据查询是通过 [Active Record](db-active-record.md) 进行的,而其是完全使用 PDO 预处理语句执行 SQL 查询的。在预处理语句中,上述示例中,构造 SQL 查询的场景是不可能发生的。 |
|
|
|
|
|
|
|
|
|
Still, sometimes you need [raw queries](db-dao.md) or [query builder](db-query-builder.md). In this case you should use |
|
|
|
|
safe ways of passing data. If data is used for column values it's preferred to use prepared statements: |
|
|
|
|
有时,你仍需要使用 [raw queries](db-dao.md) 或者 [query builder](db-query-builder.md)。在这种情况下,你应该使用安全的方式传递参数。如果数据是提供给表列的值,最好使用预处理语句: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
// query builder |
|
|
|
@ -77,97 +70,78 @@ $userIDs = $connection
|
|
|
|
|
->queryColumn(); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
If data is used to specify column names or table names the best thing to do is to allow only predefined set of values: |
|
|
|
|
|
|
|
|
|
如果数据是用于指定列的名字,或者表的名字,最好的方式是只允许预定义的枚举值。 |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
function actionList($orderBy = null) |
|
|
|
|
{ |
|
|
|
|
if (!in_array($orderBy, ['name', 'status'])) { |
|
|
|
|
throw new BadRequestHttpException('Only name and status are allowed to order by.') |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ... |
|
|
|
|
} |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
In case it's not possible, table and column names should be escaped. Yii has special syntax for such escaping |
|
|
|
|
which allows doing it the same way for all databases it supports: |
|
|
|
|
如果上述方法不行,表名或者列名应该被转义。 Yii 针对这种转义提供了一个特殊的语法,这样可以在所有支持的数据库都使用一套方案。 |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
$sql = "SELECT COUNT([[$column]]) FROM {{table}}"; |
|
|
|
|
$rowCount = $connection->createCommand($sql)->queryScalar(); |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
You can get details about the syntax in [Quoting Table and Column Names](db-dao.md#quoting-table-and-column-names). |
|
|
|
|
|
|
|
|
|
你可以在 [Quoting Table and Column Names](db-dao.md#quoting-table-and-column-names) 中获取更多的语法细节。 |
|
|
|
|
|
|
|
|
|
Avoiding XSS |
|
|
|
|
防止 XSS 攻击 |
|
|
|
|
------------ |
|
|
|
|
|
|
|
|
|
XSS or cross-site scripting happens when output isn't escaped properly when outputting HTML to the browser. For example, |
|
|
|
|
if user can enter his name and instead of `Alexander` he enters `<script>alert('Hello!');</script>`, every page that |
|
|
|
|
outputs user name without escaping it will execute JavaScript `alert('Hello!');` resulting in alert box popping up |
|
|
|
|
in a browser. Depending on website instead of innocent alert such script could send messages using your name or even |
|
|
|
|
perform bank transactions. |
|
|
|
|
XSS 或者跨站脚本发生在输出 HTML 到浏览器时,输出内容没有正确的转义。例如,如果用户可以输入其名称,那么他输入 `<script>alert('Hello!');</script>` 而非其名字 `Alexander`,所有输出没有转义直接输出用户名的页面都会执行 JavaScript 代码 `alert('Hello!');`,这会导致浏览器页面上出现一个警告弹出框。就具体的站点而言,除了这种无意义的警告输出外,这样的脚本可以以你的名义发送一些消息到后台,甚至执行一些银行交易行为。 |
|
|
|
|
|
|
|
|
|
Avoiding XSS is quite easy in Yii. There are generally two cases: |
|
|
|
|
避免 XSS 攻击在 Yii 中非常简单,有如下两种一般情况: |
|
|
|
|
|
|
|
|
|
1. You want data to be outputted as plain text. |
|
|
|
|
2. You want data to be outputted as HTML. |
|
|
|
|
|
|
|
|
|
If all you need is plain text then escaping is as easy as the following: |
|
|
|
|
1. 你希望数据以纯文本输出。 |
|
|
|
|
2. 你希望数据以 HTML 形式输出。 |
|
|
|
|
|
|
|
|
|
如果你需要的是纯文本,你可以如下简单的转义: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
<?= \yii\helpers\Html::encode($username) ?> |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
If it should be HTML we could get some help from HtmlPurifier: |
|
|
|
|
如果是 HTML ,我们可以用 HtmlPurifier 帮助类来执行: |
|
|
|
|
|
|
|
|
|
```php |
|
|
|
|
<?= \yii\helpers\HtmlPurifier::process($description) ?> |
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
Note that HtmlPurifier processing is quite heavy so consider adding caching. |
|
|
|
|
注意: HtmlPurifier 帮助类的处理过程较为费时,建议增加缓存: |
|
|
|
|
|
|
|
|
|
Avoiding CSRF |
|
|
|
|
防止 CSRF 攻击 |
|
|
|
|
------------- |
|
|
|
|
|
|
|
|
|
CSRF is an abbreviation for cross-site request forgery. The idea is that many applications assume that requests coming |
|
|
|
|
from a user browser are made by the user himself. It could be false. |
|
|
|
|
|
|
|
|
|
For example, `an.example.com` website has `/logout` URL that, when accessed using a simple GET, logs user out. As long |
|
|
|
|
as it's requested by the user itself everything is OK but one day bad guys are somehow posting |
|
|
|
|
`<img src="http://an.example.com/logout">` on a forum user visits frequently. Browser doesn't make any difference between |
|
|
|
|
requesting an image or requesting a page so when user opens a page with such `img` tag he's being logged out from |
|
|
|
|
`an.example.com`. |
|
|
|
|
CSRF 是跨站请求伪造的缩写。这个攻击思想源自许多应用程序假设来自用户的浏览器请求是由用户自己产生的,而事实并非如此。 |
|
|
|
|
|
|
|
|
|
That's the basic idea. One can say that logging user out is nothing serious. Well, sending POST isn't much trickier. |
|
|
|
|
比如说:`an.example.com` 站点有一个 `/logout` URL,当以 GET 请求访问时,登出用户。如果它是由用户自己操作的,那么一切都没有问题。但是,有一天坏人在一个用户经常访问的论坛发了一个 `<img src="http://an.example.com/logout">` 内容的帖子。浏览器无法辨别请求一个图片还是一个页面,所以,当用户打开含有上述标签的页面时,他将会从 `an.example.com` 登出。 |
|
|
|
|
|
|
|
|
|
In order to avoid CSRF you should always: |
|
|
|
|
上面就是最原始的思想。有人可能会说,登出用户也不是什么严重问题,然而,我们发送一些 POST 数据其实也不是很麻烦的事情。 |
|
|
|
|
|
|
|
|
|
1. Follow HTTP specification i.e. GET should not change application state. |
|
|
|
|
2. Keep Yii CSRF protection enabled. |
|
|
|
|
为了避免 CSRF 攻击,你总是需要: |
|
|
|
|
|
|
|
|
|
1. 遵循 HTTP 准则,比如 GET 不应该改变应用的状态。 |
|
|
|
|
2. 保证 Yii CSRF 保护开启。 |
|
|
|
|
|
|
|
|
|
Avoiding file exposure |
|
|
|
|
防止文件暴露 |
|
|
|
|
---------------------- |
|
|
|
|
|
|
|
|
|
By default server webroot is meant to be pointed to `web` directory where `index.php` is. In case of shared hosting |
|
|
|
|
environments it could be impossible to achieve so we'll end up with all the code, configs and logs in server webroot. |
|
|
|
|
默认的服务器 webroot 目录指向包含有 `index.php` 的 `web` 目录。在共享托管环境下,这样是不可能的,这样导致了所有的代码,配置,日志都在webroot目录。 |
|
|
|
|
|
|
|
|
|
If it's the case don't forget to deny access to everything except `web`. If it can't be done consider hosting your |
|
|
|
|
application elsewhere. |
|
|
|
|
如果是这样,别忘了拒绝除了 `web` 目录以外的目录的访问权限。如果没法这样做,考虑将你的应用程序托管在其他地方。 |
|
|
|
|
|
|
|
|
|
Avoiding debug info and tools at production |
|
|
|
|
在生产环境关闭调试信息和工具 |
|
|
|
|
------------------------------------------- |
|
|
|
|
|
|
|
|
|
In debug mode Yii shows quite verbose errors which are certainly helpful for development. The thing is that these |
|
|
|
|
verbose errors are handy for attacker as well since these could reveal database structure, configuration values and |
|
|
|
|
parts of your code. Never run production applications with `YII_DEBUG` set to `true` in your `index.php`. |
|
|
|
|
在调试模式下, Yii 展示了大量的错误信息,这样是对开发有用的。同样,这些调试信息对于攻击者而言也是方便其用于破解数据结构,配置值,以及你的部分代码。永远不要在生产模式下将你的 `index.php` 中的 `YII_DEBUG` 设置为 `true`。 |
|
|
|
|
|
|
|
|
|
You should never enalble Gii at production. It could be used to get information about database structure, code and to |
|
|
|
|
simply rewrite code with what's generated by Gii. |
|
|
|
|
你同样也不应该在生产模式下开启 Gii。它可以被用于获取数据结构信息,代码,以及简单的用 Gii 生成的代码覆盖你的代码。 |
|
|
|
|
|
|
|
|
|
Debug toolbar should be avoided at production unless really necessary. It exposes all the application and config |
|
|
|
|
details possible. If you absolutely need it check twice that access is properly restricted to your IP only. |
|
|
|
|
调试工具栏同样也应该避免在生产环境出现,除非非常有必要。它将会暴露所有的应用和配置的详情信息。如果你确定需要,反复确认其访问权限限定在你自己的 IP。 |
|
|
|
|