首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 輸入資料清理與 XSS 防禦:從 StrSwap 類到現代過濾器鏈的最佳實踐

     編輯:PHP 綜合知識

前言

早期 PHP 專案常見 StrSwap 等自訂類別,在進入點統一清理 $_GET/$_POST,企圖一次解決 SQL 注入、XSS、指令注入等問題。這種「全域神奇清理」違反情境輸出編碼原則,不但無法完整防禦,反而引入資料損壞、誤判、維護困難。本文分析傳統做法缺失,建立現代分層防禦體系:輸入驗證、情境輸出編碼、內容安全政策 (CSP)、資料庫參數化。

傳統 StrSwap 類別缺陷分析

<?php
// 原始碼典型問題
class StrSwap {
    function StrReplace($str) {
        $str = StripSlashes($str);                    // 1. 盲目 stripslashes,破壞合法反斜線
        $str = str_replace(chr(92), '', $str);        // 2. 移除所有反斜線,導致路徑、正則、JSON 失效
        $str = str_replace(chr(47), '', $str);        // 3. 移除斜線,破壞 URL、日期、分數
        $str = str_replace("\r\n", "
", $str); // 4. 過早 HTML 化,資料庫存入
導致 JSON/API 輸出錯誤 $str = str_replace('<', '<', $str); // 5. 僅處理 < >,漏掉屬性注入、JS 事件、CSS 注入 $str = str_replace('>', '>', $str); $str = str_replace(';', ';', $str); // 6. 全形替換破壞程式碼、CSV、語意 $str = str_replace('"', '“', $str); // 7. 破壞 JSON、HTML 屬性值、SQL 字串 $str = str_replace("'", '‘', $str); // 8. 破壞 SQL、JS 字串、人名(O'Connor) $str = str_replace(" ", " ", $str); // 9. 破壞空白語意、排版、搜尋 $str = str_replace("/**/", " ", $str); // 10. 針對特定繞過,治標不治本 return trim($str); } // ... 自動將 $_GET/$_POST 轉為全域變數,極度危險(register_globals 風格) } ?>

核心缺陷

缺陷後果正確做法
全域清理破壞合法資料(檔案路徑、程式碼、JSON、Base64、郵件內容)針對特定情境處理:資料庫用參數化、HTML 用 htmlspecialchars、JS 用 json_encode、Shell 用 escapeshellarg
過早 HTML 編碼資料庫存入 <br>,導致 API 回傳雙重編碼、搜尋失效、編輯器顯示實體碼資料庫儲存原始資料,輸出到 HTML 時才編碼
字元黑名單永遠不完整(編碼繞過、Unicode 同形字、CSS/JS 多樣化注入向量)白名單驗證 + 情境編碼
自動全域變數變數覆蓋攻擊、命名空間汙染、難以追蹤資料流明確使用 $_GET/$_POST/$_REQUEST,或框架 Request 物件
缺乏編碼感知多位元組字元(UTF-8)處理錯誤,導致截斷、亂碼使用 mb_* 函數、htmlspecialchars($str, ENT_QUOTES, 'UTF-8')

現代分層防禦架構

第 1 層:輸入驗證

在控制器/表單請求層,針對業務規則驗證:

<?php
// 使用 Laravel 風格表單請求或獨立驗證器
class StorePostRequest
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:200', 'regex:/^[\p{L}\p{N}\s\-_(),.!?]+/u'],
            'content' => ['required', 'string', 'max:50000'],
            'email' => ['required', 'email:rfc,dns'],
            'age' => ['required', 'integer', 'between:1,120'],
            'tags' => ['array', 'max:10'],
            'tags.*' => ['string', 'max:30', 'alpha_dash'],
        ];
    }

    public function messages(): array
    {
        return [
            'title.regex' => '標題包含非法字元',
        ];
    }
}

// 控制器
public function store(StorePostRequest $request)
{
    $validated = $request->validated(); // 只含通過驗證的資料
    // $validated['title'] 仍是原始字串,未經 HTML 編碼
}
?>

原則:驗證「是否符合預期格式」,而非「移除危險字元」。拒絕不合法輸入,而非試圖清理。

第 2 層:資料庫參數化查詢

<?php
// 唯一正確防禦 SQL 注入的方式
$stmt = $pdo->prepare('INSERT INTO posts (title, content, user_id) VALUES (?, ?, ?)');
$stmt->execute([$validated['title'], $validated['content'], $userId]);
// 或具名參數
$stmt = $pdo->prepare('INSERT INTO posts (title, content, user_id) VALUES (:title, :content, :uid)');
$stmt->execute(['title' => $validated['title'], 'content' => $validated['content'], 'uid' => $userId]);
?>

參數化查詢將資料與指令分離,資料庫引擎保證資料永遠不會被解釋為 SQL。

第 3 層:情境輸出編碼

輸出情境編碼函數範例
HTML 內文/屬性值htmlspecialchars($str, ENT_QUOTES, 'UTF-8')<div></div>, <input value="">
HTML 屬性值(單引號)htmlspecialchars($str, ENT_QUOTES, 'UTF-8')<input value=''>
JavaScript 字串/JSONjson_encode($str, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT)<script>const name = ;</script>
CSS 值白名單驗證或 cssesc() (paragonie/csp-builder)style="color: ;"
URL 參數rawurlencode($str)<a href="/search?q=">
Shell 指令參數escapeshellarg($str)exec('convert ' . escapeshellarg($src) . ' ' . escapeshellarg($dst))
SQL 字串字面值(極少手寫)$pdo->quote($str)僅用於無法參數化的極罕見情況

輔助函數

<?php
function h(string $str, int $flags = ENT_QUOTES | ENT_SUBSTITUTE, string $encoding = 'UTF-8'): string
{
    return htmlspecialchars($str, $flags, $encoding);
}

function js(string $str): string
{
    return json_encode($str, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE);
}

function url(string $str): string
{
    return rawurlencode($str);
}
?>

第 4 層:內容安全政策

透過 HTTP 標頭限制瀏覽器載入/執行資源來源,即使發生 XSS 也能限制傷害:

<?php
// 中介層或入口檔案
$nonce = bin2hex(random_bytes(16));
header("Content-Security-Policy: " . implode('; ', [
    "default-src 'self'",
    "script-src 'self' 'nonce-$nonce' 'strict-dynamic' https://cdn.jsdelivr.net",
    "style-src 'self' 'nonce-$nonce' https://fonts.googleapis.com",
    "img-src 'self' data: https:",
    "font-src 'self' https://fonts.gstatic.com",
    "connect-src 'self' https://api.example.com",
    "frame-ancestors 'none'",
    "base-uri 'self'",
    "form-action 'self'",
]));
// 將 nonce 傳給視圖
$GLOBALS['csp_nonce'] = $nonce;
?>

視圖中:<script nonce="">...</script>

遷移指南:從 StrSwap 到現代架構

  1. 移除全域清理:刪除 StrSwap、auto_prepend_file 等全域攔截。
  2. 審計所有資料庫查詢:確保 100% 使用參數化查詢,無字串拼接。
  3. 審計所有輸出點:將 echo $var 替換為 echo h($var)(HTML 內文/屬性)、echo js($var)(JS 內容)。
  4. 建立驗證規則:為每個輸入端點定義白名單規則(長度、格式、型別)。
  5. 部署 CSP:從 Content-Security-Policy-Report-Only 開始收集違規報告,逐步收緊。
  6. 資料清洗(一次性):若資料庫已含過早編碼的髒資料,編寫腳本批次解碼(html_entity_decode)還原原始內容。

常見誤區釐清

mysqli_real_escape_string 能防 SQL 注入?
只能防禦字串字面值注入,無法防禦識別碼(表名、欄位名)、LIMIT/OFFSET 數值注入。參數化查詢是唯一完整方案。
strip_tags() 能防 XSS?
無法防禦屬性注入(<img src=x onerror=alert(1)>)、CSS 注入、DOM 型 XSS。htmlspecialchars 才是正確方案。
HTML Purifier 能完全安全?
允許使用者輸入富文本時必要,但配置錯誤仍有風險,且效能極差。優先考慮 Markdown + 安全渲染器。
前端框架自動跳脫就安全?
Vue/React 預設跳脫插值內容,但 v-html/dangerouslySetInnerHTML、屬性綁定、URL、CSS、JS 互動點仍需手動處理。

結語

資安沒有銀彈,只有分層防禦。拋棄「全域神奇清理」迷思,擁抱輸入驗證 + 參數化查詢 + 情境輸出編碼 + CSP的現代體系。每一層都簡單、明確、可測試,組合起來才能構建真正韌性的應用程式。記住:資料本身無毒,毒在於被錯誤解釋的情境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved