以下我們將回顧常見的安全性原則,並描述在使用 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 進行的,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();
您可以在引用表名稱和欄位名稱中取得有關語法的詳細資訊。
關於該主題的進一步閱讀
當輸出 HTML 到瀏覽器時,如果輸出未正確逸出,就會發生 XSS 或跨網站腳本攻擊。例如,如果使用者可以輸入他的姓名,並且他輸入的不是 Alexander
,而是 <script>alert('Hello!');</script>
,則每個輸出使用者姓名而未對其進行逸出的頁面都將執行 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 請求。不幸的是,這並不能拯救我們,因為攻擊者可以將一些 JavaScript 程式碼放在 <img>
標籤的位置,這也允許向該 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 設定並不會使上述內容過時,因為並非所有瀏覽器都支援該設定。有關更多資訊,請參閱Session 和 Cookie sameSite 選項。
關於該主題的進一步閱讀
Yii 配置是關聯陣列,框架使用它們通過 Yii::createObject($config)
實例化新物件。這些陣列指定了實例化的類別名稱,務必確保此類別名稱不是來自不受信任的來源。否則,可能會導致不安全的反射,這是一種漏洞,它允許通過利用特定類別的載入來執行惡意程式碼。此外,當您需要將金鑰動態新增到從框架類別(例如基本 Component
類別)派生的物件時,必須使用白名單方法驗證這些動態屬性。此預防措施是必要的,因為框架可能會在 __set()
魔術方法中使用 Yii::createObject($config)
。
預設情況下,伺服器網頁根目錄應指向 web
目錄,其中包含 index.php
。在共用主機環境中,可能無法實現這一點,因此我們最終會將所有程式碼、配置和日誌都放在伺服器網頁根目錄中。
如果是這種情況,請不要忘記拒絕存取除 web
之外的所有內容。如果無法做到這一點,請考慮在其他地方託管您的應用程式。
在偵錯模式下,Yii 會顯示相當詳細的錯誤,這對於開發當然很有幫助。問題是,這些詳細的錯誤對於攻擊者來說也很方便,因為它們可能會洩露資料庫結構、配置值和您的部分程式碼。永遠不要在生產應用程式中將 YII_DEBUG
設定為 true
在您的 index.php
中執行。
您永遠不應在生產環境中啟用 Gii 或偵錯工具列。它可以用於取得有關資料庫結構、程式碼的資訊,以及簡單地使用 Gii 產生的內容重寫程式碼。
除非真正必要,否則應避免在生產環境中使用偵錯工具列。它會洩露所有可能的應用程式和配置詳細資訊。如果您絕對需要它,請再次檢查是否已將存取權限正確地限制為僅限您的 IP。
關於該主題的進一步閱讀
Yii 提供了依賴 Cookie 和/或 PHP Session 的功能。如果您的連線遭到入侵,這些功能可能會很脆弱。如果應用程式通過 TLS(通常稱為 SSL)使用安全連線,則風險會降低。
請參閱您的網頁伺服器文件,以取得有關如何配置它的說明。您也可以查看 H5BP 專案提供的範例配置
注意:當配置 TLS 時,建議(Session)Cookie 僅通過 TLS 發送。這可以通過為 Session 和/或 Cookie 設定
secure
標誌來實現。有關更多資訊,請參閱Session 和 Cookie secure 標誌。
本節的目的是重點說明在建立用於服務基於 Yii 的網站的伺服器配置時需要考慮的風險。除了此處涵蓋的要點之外,可能還有其他與安全性相關的配置選項需要考慮,因此不要將本節視為完整。
Host
-header 攻擊 ¶諸如 yii\web\UrlManager 和 yii\helpers\Url 之類的類別可能會使用當前請求的主機名稱來產生連結。如果網頁伺服器配置為獨立於 Host
標頭的值來服務同一個網站,則此資訊可能不可靠,並且可能會被發送 HTTP 請求的使用者偽造。在這種情況下,您應該修復您的網頁伺服器配置以僅針對指定的主機名稱服務網站,或者通過設定 request
應用程式組件的 hostInfo 屬性來顯式設定或過濾該值。
有關伺服器配置的更多資訊,請參閱您的網頁伺服器文件
如果您無法存取伺服器配置,您可以在應用程式層級設定 yii\filters\HostControl 過濾器,以防止此類攻擊
// Web Application configuration file
return [
'as hostControl' => [
'class' => 'yii\filters\HostControl',
'allowedHosts' => [
'example.com',
'*.example.com',
],
'fallbackHostInfo' => 'https://example.com',
],
// ...
];
注意:對於「主機標頭攻擊」保護,您應始終首選網頁伺服器配置,而不是過濾器用法。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 上編輯它 !
註冊或登入以進行評論。