展開文件目錄

安全

Web 應用程式安全

總是有些不懷好意的人想鑽你 Web 應用程式的漏洞,所以採取一些必要的防範措施來強化 Web 應用程式的安全性是相當重要的。很幸運的, The Open Web Application Security Project (OWASP) 已經整理一份相當詳細的資安列表以及防範的對策。如果你是一個有資安意識的開發者,那麼就必須詳細的閱讀它。

密碼雜湊

每個人在建構 PHP 應用程式時終究會加入使用者登入的功能。使用者的帳號及密碼都會被儲存在資料庫,並在登入時使用該資料來驗證使用者。

在寫入資料前正確的 雜湊 他們的密碼是相當重要的一件事。使用單向雜湊函式處理密碼可以產生不可逆的密碼雜湊,而該雜湊會是一段固定長度的字串且無法逆向演算出密碼。這就代表你可以雜湊另一組密碼,並比較兩者是否來自於同一組密碼,而且你無法得知原始的密碼。如果你不將密碼雜湊,那麼當未經授權的第三者進入你的資料庫時,所有使用者的帳號資料將會一覽無遺。有些使用者可能(很不幸的)在別的服務也使用相同的密碼,所以務必要重視資訊安全的問題。

使用 password_hash 來雜湊密碼

password_hash 已經在 PHP 5.5 時加入。現在你可以使用目前 PHP 所支援的演算法中最強大的 BCrypt 。當然,未來支援更多的演算法時他就不見得會是最強的。 password_compat 函式庫的出現是為了提供 PHP >= 5.3.7 對舊版本的支援性。

在下面例子中,我們雜湊一組字串,然後和新的雜湊值做比對。因為我們使用的兩組字串是不同的( 'secret-password' 與 'bad-password' ),所以理所當然的就登入失敗了。

<?php

require 'password.php';

$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct Password
} else {
    // Wrong password
}

資料過濾

絕對(永遠)不要在 PHP 程式相信外部輸入。每次在程式中使用外部輸入時務必要過濾及驗證。 filter_varfilter_input 函式可以過濾文字及驗證文字的格式(例如 Email )。

外部輸入可以是:由表單輸入的 $_GET$_POST ,還有 $_SERVER 超級全域變數,以及來自 fopen('php://input', 'r') 的 Http 請求。切記,外部輸入並不限定是使用者從表單填入的資料。上傳及下載的檔案, Session 值及 Cookie 資料,以及第三方 Web 服務提供的資料,均是外部輸入。

當外部輸入被儲存、合併,或是下次讀取,他們依然是外部輸入。每次在程式中處理、輸出、串接或是引入資料時,試著問你自己這些資料都已經過濾,而且可以被信任。

根據資料使用的方式不同,就要進行不同的過濾。例如當外部輸入未經過濾輸出到 HTML 頁面上時,他就可以在你的網站上執行 HTML 及 JavaScript !這就是我們所知道的跨網站指令碼( Cross-site scripting , 又稱為 XSS ),這是一個相當危險的攻擊手法。避免 XSS 的其中一個方式就是使用 strip_tags 函式來過濾外部輸入的所有 HTML 標籤,另外也可以使用 htmlentitieshtmlspecialchars 函式將特定字元替換成 HTML 的實體符號。

另一個例子是傳遞給終端機執行的選項,這是相當危險的一件事(而且通常是個爛主意),不過你可以使用內建的 escapeshellarg 函式來過濾執行終端機的參數。

最後一個例子就是根據外部輸入從檔案系統載入檔案,該動作可以透過修檔案名稱或是路徑來攻擊。你必須移除外部輸入的 "/" , "../" , 空字元 或是其他檔案路徑的字元,這樣就可以避免載入隱密,非公開或是敏感的檔案。

淨化

淨化就是刪除(或是跳脫)外部輸入不合法或是不安全的字元。

例如,在將外部輸入的字元輸出至 HTML 或是插入到SQL的查詢前淨化外部輸入。當你使用 PDO 綁定參數時,他會為你淨化輸入的資料。

有時需要允許外部輸入包含某些安全的 HTML 標籤,並輸出至HTML頁面上。這是很難做到的,可以試著使用其他限制較嚴格的格式,例如 Markdown 或 BBCode 。如果真的窮途末路了,可以使用 HTML Purifier 來進行淨化。

查閱淨化過濾

驗證

驗證可以確保外部輸入是你所期望的資料。例如,你可能需要在處理註冊帳號時驗證Email,電話號碼或年齡。

查閱驗證過濾

設定檔

當你的應用程式建立設定檔時,建議遵照以下最佳實踐:

  • 設定檔應該儲存在 Web 不能直接存取及上傳的檔案目錄中
  • 如果設定檔只能放在文件的根目錄,請使用 .php 作為檔案的副檔名。這樣可以確保即使檔案被直接存取,內容也不會被當作純文字輸出。
  • 通過加密或是調整檔案系統權限讓設定檔的內容應該得到應有的保護。

註冊全域變數

注意: 從 PHP 5.4.0 開始, register_globals 的設定已經被移除且不再被支援。如果你還保留這個設定,就意味著你該更新你的應用程式了。

當開啟 register_globals 設定時,$_POST$_GET$_REQUEST 中的變數會自動註冊為全域變數,此時你的應用程式將無法辨識資料的原始來源,導致相當容易產生安全的漏洞。

例如: $_GET['foo'] 會被註冊為全域變數 $foo ,他將會覆蓋掉程式中未定義的變數。 如果你的使用的 PHP < 5.4.0 ,請再三確認已經把 register_globals關閉

錯誤報告

錯誤報告可以用來找到應用程式的錯誤,但是也可能將應用程式的結構暴露在外,產生安全性的問題。為了防止這類問題的發生,你需要為你的開發環境及上線環境進行不同的設定。

開發環境

如果要在開發環境顯示錯誤訊息,你需要在 php.ini 中進行以下設定:

display_errors = On
display_startup_errors = On
error_reporting = -1
log_errors = On

設定為 -1 表示顯示任何的錯誤訊息,包括未來版本新增的錯誤類型或參數,和 PHP 5.4 中的 E_ALL 相同。 - php.net

E_STRICT 錯誤參數是在 5.3.0 中加入的,並不包含在 E_ALL 中,但是在 5.4.0 開始,就被包含進 E_ALL 裡。這是什麼意思? 這就表示如果要在 PHP 5.3 中顯示所有的錯誤訊息,你必須將參數設定為 -1E_ALL | E_STRICT

各個 PHP 版本中顯示所有錯誤的設定

  • < 5.3 -1E_ALL
  •   5.3 -1E_ALL | E_STRICT
  • > 5.3 -1E_ALL

線上環境

如果要在線上環境隱藏錯誤訊息,你需要在 php.ini 中進行以下設定:

display_errors = Off
display_startup_errors = Off
error_reporting = E_ALL
log_errors = On

在線上環境進行這個設定後,所有的錯誤訊息會紀錄至 Web 伺服器的錯誤日誌,但不會顯示至使用者畫面。欲了解更多的錯誤設定相關資訊,請參考 PHP 手冊: