HOWTO · PHP

在 PHP 中清理輸入

在今天的帖子中,我們將學習如何在將輸入資料傳遞到資料庫之前清理 PHP 中的輸入資料。

本頁內容

沒有任何通用步驟能讓所有不可信的值都變得安全。先判斷值代表什麼,再依照該欄位的要求驗證;只有在應用程式有明確規則時才進行正規化;顯示值時,依輸出情境進行編碼;將值送往資料庫時,使用參數化查詢。這些操作解決的是不同問題:驗證會接受或拒絕值,清理會轉換值,輸出編碼則會針對特定情境安全地表示字元。這些操作都無法單獨保證資料完整性,也無法保護值的所有用途。

PHP 輸入清理能做什麼,不能做什麼

處理輸入時,應先確認欄位的要求。年齡、電子郵件地址、顯示名稱和搜尋字詞,各自有不同的有效格式。驗證會檢查值是否符合欄位要求;正規化則是依照明確記錄的規則進行有意的轉換,例如移除字串前後的空白。清理器可能會移除或轉換字元,但這可能在不知不覺中改變使用者提交的內容,也不代表處理後的結果適合所有後續用途。

若應用程式需要保留原始值,請將它與正規化或編碼後的表示方式分開保存。不要只因字元看起來不尋常就將它移除:姓名及其他合法文字可能包含標點符號或非 ASCII 字元。也應在實際使用值時選擇適當的處理方式。HTML 輸出需要 HTML 編碼,SQL 值則需要查詢參數。這兩種防護不能互相取代。

顯示使用者輸入時如何跳脫 HTML

將文字放入 HTML 文字節點或加上引號的屬性時,應先進行編碼。對一般 HTML 輸出而言,htmlspecialchars() 會轉換具有 HTML 特殊意義的字元,包括和號、角括號,以及使用適當旗標時的引號。htmlentities() 也會編碼具有具名 HTML 實體的字元。這兩個函式都不是通用的 HTML 清理工具:為文字節點編碼,不代表值也能安全地放入 JavaScript、CSS、網址或原始 HTML 標記。請依照確切的輸出情境,採用合適的編碼函式與規則。

以下獨立範例會為 HTML 文字節點中的姓名進行編碼:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

<?php
$name = 'Ada & Sam';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'), PHP_EOL;
Ada &amp; Sam

提供的執行證據確認了這個輸出。這是一個供 HTML 文字情境使用的字串,不應拿來當作 SQL 或 JavaScript 的值。

使用 filter_var() 驗證及正規化輸入

filter_var() 會執行選定的驗證或篩選操作,不是 HTML 清理工具。例如,filter_var($email, FILTER_VALIDATE_EMAIL) 會在電子郵件符合篩選條件時傳回該值,不符合時則傳回 false。filter_var($count, FILTER_VALIDATE_INT) 可以驗證整數;如果應用程式需要指定範圍,也可以使用相關選項。請明確檢查傳回值,因為 false 代表驗證失敗。

清理型篩選器可能會轉換輸入,而不是證明輸入有效。例如,FILTER_SANITIZE_EMAIL 會移除該篩選器不允許的字元,但無法證明產生的地址屬於某位使用者,或確實能收到郵件。如果應用程式選擇正規化輸入,應先定義規則,再驗證處理後的結果。不要依賴 FILTER_SANITIZE_STRING 作為通用解法;移除標記不會提供符合情境的輸出編碼,也不會讓任意內容變得安全。

使用預備陳述式執行 SQL

使用參數化查詢,將 SQL 語法與使用者提供的值分開。使用 PDO 時,先準備含有佔位符的查詢,執行時再另外傳入值。例如,模式如下:$statement = $pdo->prepare('SELECT id, email FROM Users WHERE lastname = :lastname');,接著執行 $statement->execute(['lastname' => $lastname]);。這只是示意片段:$pdo 必須已連線至預期的資料庫;資料庫服務與結構不在此範例的涵蓋範圍內。

PDO 不會清理任意輸入。對查詢中的值而言,參數綁定是防止 SQL 注入的措施,但不會驗證值是否符合應用程式需求。請依欄位規則驗證值;對資料表或欄位名稱等識別字,則採用不同的查詢建構方式,因為這類識別字通常不能作為值參數傳入。MySQLi 也支援預備陳述式;使用該介面時,請採用它的參數綁定 API。

依預期型別處理個人資料欄位

個人資料的每個欄位都應套用各自的規則。驗證電子郵件地址;必要時,再透過應用程式流程確認地址歸屬。只有在政策如此規定時,才移除顯示名稱前後的空白。郵遞區號應依相關地區驗證,不要假設全球格式都相同。數值欄位則要先解析並檢查允許範圍,再供後續使用。欄位允許時,應保留合法的標點符號及 Unicode 字元。

在頁面上顯示已儲存的個人資料時,即使提交時已驗證,也要在輸出時依特定 HTML 情境進行編碼。查詢個人資料時,則透過預備陳述式傳入值。將驗證、正規化、資料庫參數化和輸出編碼視為彼此獨立的決策,能讓處理方式更容易理解,也能避免把某種轉換誤認為通用防護。