PHP 以部署容易、生態豐富成為 Web 開發主流,但其鬆綁的語法特性也讓新手易寫出不安全代碼。本文歸納 5 項必須落實的安全實務,涵蓋錯誤報告、環境設定、輸入驗證、XSS 與 SQL 注入防禦,助你建立纏深防禦體系。
開發環境:開啟完整錯誤回報,作為除錯與漏洞掃描工具。
// 開發/測試環境
error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/logs/php_error.log');
正式環境:關閉畫面顯示,僅記錄日誌,避免洩露路徑、資料庫結構等敏感資訊。
// 正式環境
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT);
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php/php_error.log');
原則:使用者看不到任何 PHP 錯誤訊息,開發者能在日誌完整追蹤。
以下指令在 PHP 5.4+ 已移除,但舊專案或舊主機可能仍開啟,務必確認關閉:
| 指令 | 危害 | 建議值 |
|---|---|---|
register_globals | 自動將 GET/POST/COOKIE 變為全域變數,極易造成變數覆蓋與注入 | Off |
magic_quotes_gpc | 自動加反斜線,導致雙重轉義、資料汙染,且給開發者虛假安全感 | Off |
magic_quotes_runtime | 執行期從外部資源讀取資料時自動轉義 | Off |
magic_quotes_sybase | 以雙單引號替代反斜線 | Off |
設定方式(php.ini 優先,.htaccess 次之):
; php.ini
register_globals = Off
magic_quotes_gpc = Off
magic_quotes_runtime = Off
magic_quotes_sybase = Off
白名單驗證:只允許預期格式,拒絕其他。
// 範例:驗證電子郵件
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
if ($email === false) {
throw new InvalidArgumentException('Email 格式錯誤');
}
// 範例:驗證整數 ID
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 1000000]
]);
if ($id === false || $id === null) {
http_response_code(400);
exit('無效的 ID');
}
輸出編碼:根據輸出語境編碼,而非輸入時過度過濾。
htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')json_encode($str)(含標籤)urlencode($str)escapeshellarg($str) / escapeshellcmd($str)核心原則:永遠不信任使用者輸入,輸出時才根據語境編碼。
<?php
// 錯誤示範:直接輸出
echo "<div>{$_GET['msg']}</div>";
// 正確示範:HTML 語境編碼
$msg = htmlspecialchars($_GET['msg'] ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
echo "<div>{$msg}</div>";
// 若需允許有限 HTML(如部落格編輯器),請使用 HTML Purifier
require_once 'HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($dirtyHtml);
HTTP 標頭加強:
// 防止瀏覽器 MIME 嗅探
header('X-Content-Type-Options: nosniff');
// 啟用瀏覽器內建 XSS 過濾器 (舊版瀏覽器)
header('X-XSS-Protection: 1; mode=block');
// 內容安全政策 (CSP) - 現代瀏覽器最強防線
header("Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;");
錯誤寫法(字串拼接):
$sql = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "' AND password = '" . md5($_POST['password']) . "'";
正確寫法(PDO 預處理):
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->execute([$_POST['username'], password_hash($_POST['password'], PASSWORD_DEFAULT)]);
$user = $stmt->fetch();
進階防護**:
SELECT, INSERT, UPDATE, DELETE,禁止 DROP, ALTER, GRANT。display_errors,避免洩露 SQL 結構。| 層面 | 措施 |
|---|---|
| Session | session.cookie_httponly=1, session.cookie_secure=1 (HTTPS), session.use_strict_mode=1 |
| 檔案上傳 | 驗證 MIME、副檔名、大小;儲存於非公開目錄;重新命名;禁用腳本執行權限 |
| CSRF | 所有狀態改變請求 (POST/PUT/DELETE) 需驗證 CSRF Token |
| 密碼儲存 | 僅用 password_hash() + password_verify(),禁止 MD5/SHA1 |
| 目錄權限 | 遵循最小權限:目錄 755,檔案 644,上傳目錄 755 但禁用 PHP 執行 |
| 禁用函式 | disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,dl,... |
安全非一次性工作,而是持續的流程。落實「錯誤分級、禁用歷史遺毒、白名單驗證、語境編碼輸出、參數化查詢」五大鐵律,再疊加纏深防禦層,即能阻擋 99% 以上常見攻擊。記住:沒有絕對安全,只有不斷減少攻擊面。