上傳目錄、緩存目錄、靜態資源目錄絕不應允許 PHP 腳本執行。通過 Nginx location 匹配優先級,在 FastCGI 處理前攔截並拒絕訪問。
# 單個目錄
location ~ ^/uploads/.*\.php$ {
deny all;
return 403;
}
# 多個目錄(正則分組)
location ~ ^/(uploads|runtime|cache|data|assets)/.*\.php$ {
deny all;
access_log off;
return 403;
}
# 更嚴格:禁止任意 .php/.php5/.phtml 等後綴
location ~ ^/(uploads|runtime)/.*\.(php|php5|phtml|phar|inc)$ {
deny all;
return 403;
}
server {
listen 80;
listen 443 ssl http2;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.php index.html;
# SSL 配置略...
# 【關鍵】安全拦截:必須放在 PHP location 之前!
# 優先級:精確匹配 = 前綴匹配 ~ 正則匹配
# 此處用正則 ~,需確保順序在 ~ \.php$ 之前
# 1. 禁止上傳/緩存/運行時目錄執行 PHP
location ~ ^/(uploads|runtime|cache|storage|data|assets|public/uploads)/.*\.(php|php5|phtml|phar|inc|shtml)$ {
deny all;
access_log off;
return 403;
}
# 2. 禁止隱藏文件、版本控制目錄訪問
location ~ /\.(git|svn|env|htaccess|htpasswd) {
deny all;
return 404;
}
# 3. 靜態資源長期緩存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|webp|woff|woff2|svg|map)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# 4. PHP 處理(FastCGI)
location ~ \.php$ {
# 安全:防止錯誤路徑信息洩露
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.2-fpm.sock; # 或 127.0.0.1:9000
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# 安全頭
fastcgi_hide_header X-Powered-By;
fastcgi_param PHP_VALUE "expose_php=off";
}
# 5. 前端路由支援(Vue/React/Laravel SPA)
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# 6. 拒絕訪問敏感文件
location ~* \.(env|log|sql|bak|ini|conf|sh)$ {
deny all;
return 404;
}
}
Nginx location 匹配優先級(高→低):
= 精確匹配^~ 前綴匹配(不再檢查正則)~ / ~* 正則匹配(按配置文件順序)/ 通用匹配因此:禁止執行的正則 location 必須寫在 location ~ \.php$ 之前,否則請求會先進入 PHP 處理器,再由 PHP 返回 404,無法攔截。
# 1. 語法檢查
nginx -t
# 2. 熱加載
nginx -s reload
# 3. 測試攔截
curl -I https://example.com/uploads/shell.php
# 預期:HTTP/1.1 403 Forbidden
# 4. 測試正常 PHP
curl -I https://example.com/index.php
# 預期:HTTP/1.1 200 OK
編輯 php-fpm.conf 或池配置 www.conf:
; 限制腳本執行目錄
php_admin_value[open_basedir] = /var/www/example.com/:/tmp/:/proc/
; 禁用危險函數
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,pcntl_exec,ld,dl
| 誤區 | 後果 | 正確做法 |
|---|---|---|
配置寫在 location ~ \.php$ 之後 | 攔截不生效,PHP 先執行 | 移至 server 塊開頭、PHP location 之前 |
只匹配 .php,漏掉 .php5/.phtml/.phar | 攻擊者用其他後綴繞過 | 正則包含所有 PHP 相關後綴 |
用 location /uploads { deny all; } | 連靜態資源(圖片/CSS)也無法訪問 | 精確匹配 .*\.php$ 後綴 |
未設置 try_files $uri =404; | 不存在的 .php 請求傳給 FPM,浪費資源 | PHP location 內必加 |
安全金句:防禦深度 = Nginx 層攔截 + PHP-FPM open_basedir + 文件系統權限 (chmod 750 + 所有者 www-data) + 禁用函數 + 代碼審計。單層防禦必有漏洞。