open_basedir 是 PHP 內建的**路徑訪問控制機制**,限制腳本能讀寫的文件系統路徑。它不依賴操作系統用戶權限,而是在 **Zend 引擎文件操作函數層面**攔截,適用於所有 SAPI (mod_php, PHP-FPM, CGI, CLI)。
; 多目錄分隔符:Windows 分號 ; Linux/Unix 冒號 :
open_basedir = "/var/www/site1:/tmp:/usr/share/php"
; 特殊值 "." 表示「腳本所在目錄」(CWD),動態適應各虛擬主機
open_basedir = ".:/tmp:/usr/share/php"
影響函數:fopen, file_get_contents, include, require, session_save_path, upload_tmp_dir, exec() 等所有文件系統操作。**不影響**網絡操作、數據庫連接。
| 層級 | 配置位置 | 優先級 | 適用場景 |
|---|---|---|---|
| 1 (最低) | php.ini | 基礎全局限制 | 單站點服務器/統一基線 |
| 2 | Apache <VirtualHost> 中 php_admin_value | 覆蓋 php.ini | 虛擬主機級隔離 (推薦) |
| 3 (最高) | Apache <Directory> 中 php_admin_value | 覆蓋 VirtualHost | 目錄級精細控制/特殊需求 |
php_value vs php_admin_valuephp_value:可被腳本內 ini_set() 修改,**不安全**php_admin_value:不可被腳本修改,強制生效,**必須用此指令**<VirtualHost *:80>
ServerName site1.example.com
DocumentRoot /var/www/site1
# 核心隔離:限制在站點目錄 + 臨時目錄 + PHP 共享庫
php_admin_value open_basedir "/var/www/site1:/tmp:/usr/share/php"
# 可選:獨立 session/upload 目錄防混淆
php_value session.save_path "/var/www/site1/tmp/session"
php_value upload_tmp_dir "/var/www/site1/tmp/upload"
<Directory /var/www/site1>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName site2.example.com
DocumentRoot /var/www/site2
php_admin_value open_basedir "/var/www/site2:/tmp:/usr/share/php"
# ...
</VirtualHost>
FPM 池配置 www.conf 或站點專用池:
; site1.conf
[site1]
user = www-site1
group = www-site1
listen = /run/php/php8.1-fpm-site1.sock
php_admin_value[open_basedir] = /var/www/site1:/tmp:/usr/share/php
php_admin_value[session.save_path] = /var/www/site1/tmp/session
php_admin_value[upload_tmp_dir] = /var/www/site1/tmp/upload
配合 Nginx fastcgi_pass unix:/run/php/php8.1-fpm-site1.sock; 實現**進程級+路徑級雙重隔離**。
| 路徑 | 用途 | 缺失後果 |
|---|---|---|
/tmp (或 sys_get_temp_dir()) | 上傳文件暫存、Session 文件、臨時緩存 | 文件上傳失效、Session 無法寫入、tempnam() 失敗 |
/usr/share/php (或 PEAR/Composer vendor 目錄) | 系統級 PHP 庫、Composer 全局安裝包 | require 'vendor/autoload.php' 失敗、框架啟動報錯 |
. (CWD) | 腳本所在目錄及子目錄 | 無法讀取同目錄配置、模板、緩存文件 |
<?php
// test_open_basedir.php - 放在站點目錄下訪問
$testPaths = [
__DIR__ . '/test.txt', // 應通過
__DIR__ . '/../other_site/secret.txt', // 應失敗 (open_basedir restriction)
'/etc/passwd', // 應失敗
'/tmp/test_write.txt', // 應通過
];
foreach ($testPaths as $path) {
$result = @file_put_contents($path, 'test') ? '✅ 成功' : '❌ 失敗';
echo "$path : $result
";
}
// 測試 include
@include '/etc/passwd' ? print('Include 系統文件: ✅') : print('Include 系統文件: ❌');
預期輸出:只有站點目錄下和 /tmp 成功,其他全部 ❌ 失敗並報 Warning: open_basedir restriction in effect。
; php.ini 關鍵安全配置 (生產環境基線)
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,pcntl_exec,dl
disable_classes =
expose_php = Off
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
session.cookie_httponly = 1
session.cookie_secure = 1 ; 僅 HTTPS
session.use_strict_mode = 1
allow_url_fopen = Off
allow_url_include = Off
| 現象 | 原因 | 解決 |
|---|---|---|
| 配置生效但報錯 | 路徑缺少尾部斜槿/實際路徑不匹配 | 用 realpath() 確認真實路徑,確保包含所有父目錄 |
| FPM 重啟後失效 | 修改了 php.ini 但未 reload FPM | systemctl reload php8.1-fpm 或發送 USR2 信號 |
| 软链接繞過 | open_basedir 檢查 realpath,软链接目標若不在允許列表仍被攔截 | 正常行為,無需修復 |
| Composer vendor 讀取失敗 | vendor 目錄不在 open_basedir 內 | 將 vendor 目錄加入配置,或部署時 composer install --working-dir=/var/www/site1 |
open_basedir 是共享主機/多站點服務器**最低成本、最高性價比**的隔離手段。它不替代操作系統用戶隔離、容器化、SELinux 等深層防禦,但在 PHP 層面構建了關鍵的「最後一道防線」。配合 disable_functions、獨立 FPM 池、只讀文件系統掛載,**小成本構建企業級安全基線**。記住:**安全不是產品,是流程;配置不是終點,是起點。**