Web 應用安全是系統設計的核心環節。本文系統整理 PHP 四大核心安全策略:檔案系統防護、資料庫注入防禦、使用者輸入過濾、執行環境加固,提供具體程式碼範例與最佳實踐清單。
<?php
// ❌ 危險代碼:直接使用使用者輸入刪除檔案
$username = $_POST['user_submitted_name'];
$userfile = $_POST['user_submitted_filename'];
$homedir = "/home/$username";
unlink("$homedir/$userfile"); // 攻擊者輸入 ../etc/passwd 可刪除系統檔案
?>
<?php
// ✅ 1. 最小權限原則:PHP 執行身份僅擁有必要目錄讀寫權
// 系統層面設定,非程式碼控制
// ✅ 2. 輸入驗證與清理
function secureDelete($username, $filename) {
// 僅允許英數字、底線、連字號、點
if (!preg_match('/^[\w\-\.]+$/', $filename)) {
throw new InvalidArgumentException('非法檔案名稱');
}
// 使用 realpath 解析真實路徑並確認在允許目錄內
$baseDir = realpath("/home/$username");
$targetPath = realpath($baseDir . DIRECTORY_SEPARATOR . $filename);
if ($targetPath === false || strpos($targetPath, $baseDir) !== 0) {
throw new RuntimeException('路徑遍歷攻擊嘗試');
}
// 使用隨機檔名儲存,資料庫記錄對應關係
// unlink($targetPath); // 實際刪除
}
// ✅ 3. 避免直接操作檔案系統
// 使用資料庫記錄檔案中繼資料,軟刪除 (deleted_at 欄位)
// 定期離線任務清理實體檔案
?>
<?php
// ❌ 危險:字串拼接
$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
// ✅ PDO 參數化查詢 (推薦)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false // 關鍵:使用真實預備語句
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = ? AND password = ?');
$stmt->execute([$username, $password]);
$user = $stmt->fetch();
// ✅ 命名參數 (可讀性佳)
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :user AND password = :pass');
$stmt->execute([':user' => $username, ':pass' => $password]);
// ✅ MySQLi 參數化
$mysqli = new mysqli('localhost', 'user', 'pass', 'test');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE username = ? AND password = ?');
$stmt->bind_param('ss', $username, $password);
$stmt->execute();
$result = $stmt->get_result();
?>
password_hash() (bcrypt/argon2),非對稱加密儲存金鑰<?php
// ✅ HTML 內容輸出編碼
function h($str) {
return htmlspecialchars($str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
// 使用
echo "<div>" . h($userInput) . "</div>";
// ✅ JavaScript 內容輸出
function jsEscape($str) {
return json_encode($str); // 含引號,直接嵌入 JS 字串
}
echo "<script>var input = " . jsEscape($userInput) . ";</script>";
// ✅ URL 參數編碼
echo "<a href='?search=" . urlencode($keyword) . "'>搜尋</a>";
// ✅ CSS/Style 屬性編碼 (需專用函數)
?>
<?php
// ✅ 僅允許預期格式
function validateUsername($input) {
// 僅英數底線,3-20 字元
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $input);
}
function validateEmail($input) {
return filter_var($input, FILTER_VALIDATE_EMAIL) !== false;
}
function validateUrl($input) {
// 僅允許 http/https 協定
return filter_var($input, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED)
&& in_array(parse_url($input, PHP_URL_SCHEME), ['http', 'https']);
}
?>
<?php
// Session 中儲存 Token
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// 表單中嵌入
echo "<input type='hidden' name='csrf_token' value='" . h($_SESSION['csrf_token']) . "'>";
// 驗證
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
die('CSRF Token 驗證失效');
}
// 處理表單...
}
?>
<?php
// composer require ezyang/htmlpurifier
require_once 'vendor/autoload.php';
$config = HTMLPurifier_Config::createDefault();
$config->set('HTML.AllowedElements', 'p,b,i,em,strong,a,ul,ol,li,br,span');
$config->set('HTML.AllowedAttributes', 'a.href,a.title,span.style');
$purifier = new HTMLPurifier($config);
$cleanHtml = $purifier->purify($dirtyHtml);
// 允許安全 HTML 標籤,移除 script, iframe, on* 事件等危險內容
?>
; 生產環境必備設定
display_errors = Off ; 不向瀏覽器顯示錯誤
display_startup_errors = Off
error_reporting = E_ALL ; 記錄所有錯誤
log_errors = On
error_log = /var/log/php/error.log ; 專用錯誤日誌
expose_php = Off ; 隱藏 PHP 版本資訊
allow_url_fopen = Off ; 禁止遠端檔案包含
allow_url_include = Off
session.cookie_httponly = 1 ; 防 XSS 竊取 Session
session.cookie_secure = 1 ; HTTPS 環境必開
session.cookie_samesite = Strict ; 防 CSRF (PHP 7.3+)
session.use_strict_mode = 1 ; 拒絕未初始化 Session ID
session.gc_maxlifetime = 1440 ; 24 分鐘 GC
; 已移除的危險特性 (PHP 5.4+ 已移除)
; register_globals = Off
; magic_quotes_gpc = Off
<?php
// ✅ 嚴格型別宣告 (PHP 7+)
declare(strict_types=1);
// ✅ 關閉錯誤顯示 (雙重保險)
ini_set('display_errors', '0');
// ✅ 設定自定義錯誤處理器
set_error_handler(function($severity, $message, $file, $line) {
error_log("PHP Error [$severity]: $message in $file:$line");
return true; // 不執行內建處理
});
set_exception_handler(function(Throwable $e) {
error_log("Uncaught Exception: " . $e->getMessage());
// 生產環境顯示友善錯誤頁
http_response_code(500);
include __DIR__ . '/templates/500.php';
exit;
});
// ✅ 使用最新 PHP 版本
// PHP 8.1+ 內建 fiber, enum, readonly, never return type 等安全增強特性
?>
| 類別 | 檢查項目 | 驗證方式 |
|---|---|---|
| 檔案系統 | 上傳檔案型別/大小/儲存路徑驗證 | 嘗試上傳 .php、超大檔、路徑遍歷 |
| 資料庫 | 所有查詢使用參數化 | 代碼審查、SQLMap 掃描 |
| 輸入過濾 | 所有輸出經過編碼 | XSS Payload 測試、CSP 標頭檢查 |
| CSRF | 狀態改變操作有 Token 驗證 | 跨站提交測試 |
| 認證授權 | 密碼強度、限流、Session 安全 | 暴力破解測試、Session 劫持測試 |
| 環境 | php.ini 安全設定生效 | phpinfo() 驗證、滲透測試 |
| 依賴 | Composer 套件無已知漏洞 | composer audit、Snyk 掃描 |
安全是持續過程而非一次性任務。建立 SDL (Security Development Lifecycle),將安全實踐融入開發全生命週期。