PHP 底層基於 C 語言,字符串以 Null 字節 (\0) 結尾。若用戶輸入包含 Null 字節通過文件系統函數處理,可能繞過邏輯檢查,導致任意文件讀取/包含漏洞。
<?php
// 易受攻擊代碼
$file = $_GET['file']; // 用戶傳入: "../../etc/passwd\0"
if (file_exists('/home/wwwrun/' . $file . '.php')) {
// file_exists 看到: /home/wwwrun/../../etc/passwd.php (實際: /etc/passwd)
// 因為 C 函數在 \0 截斷,忽略後綴 .php
include '/home/wwwrun/' . $file . '.php';
// 實際包含: /etc/passwd
}
?>
include/require:繞過後綴限制包含敏感文件file_exists/is_file:繞過存在性檢查fopen/file_get_contents:讀取任意文件unlink/rename:刪除/重命名任意文件move_uploaded_file:繞過上傳驗證<?php
$file = $_GET['file'];
$allowed = ['home', 'about', 'contact', 'products'];
if (in_array($file, $allowed, true)) {
include "/home/wwwrun/{$file}.php";
} else {
include "/home/wwwrun/home.php"; // 默認頁面
}
<?php
// PHP 5.3.4+ 內部函數已自動處理,但建議顯式過濾
$file = str_replace(chr(0), '', $_GET['file']);
// 或
$file = preg_replace('/\0+/', '', $_GET['file']);
// 通用過濾函數
function remove_null_bytes($input) {
return is_array($input)
? array_map('remove_null_bytes', $input)
: str_replace("\0", '', $input);
}
$_GET = remove_null_bytes($_GET);
$_POST = remove_null_bytes($_POST);
$_REQUEST = remove_null_bytes($_REQUEST);
<?php
$base = '/home/wwwrun/';
$requested = $base . $_GET['file'] . '.php';
// 獲取真實絕對路徑
$real = realpath($requested);
// 確保在基礎目錄內
if ($real && strpos($real, $base) === 0) {
include $real;
} else {
die('非法路徑');
}
詳細錯誤輸出可能洩露:
| 層級 | 策略 | 實現 |
|---|---|---|
| 開發環境 | 顯示詳細錯誤 | error_reporting(E_ALL); ini_set('display_errors', 1); |
| 測試/預發布 | 記錄詳細日誌,頁面友好提示 | log_errors=On; display_errors=Off |
| 生產環境 | 記錄關鍵日誌,用戶見通用頁面 | 自定義錯誤處理器 + 監控告警 |
set_error_handler(function($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) return false;
$log = "[ERROR] $message in $file:$line\n";
error_log($log, 3, '/var/log/php/app_errors.log');
// 生產環境不輸出詳情
if (ENV_PRODUCTION) {
http_response_code(500);
include 'templates/500.php';
exit;
}
return false; // 繼續標準處理
});
核心原則:永遠不信任外部輸入,採用白名單驗證,最小權限原則操作文件系統,分環境差異化錯誤處理。