适用场景
Nginx 上传目录禁执行配置实战适用于所有允许用户上传文件的站点:头像、附件、图片、简历、工单附件等目录。即使上传接口已做文件类型校验,攻击者仍可能借助解析漏洞、双扩展名、条件竞争等手法把 WebShell 送进上传目录——此时只要该目录下的 PHP 不被解析执行,WebShell 就只是一堆无害文本。本方案同样适用于 WordPress、Discuz、DedeCMS 等成熟程序的安全加固,以及历史遗留的旧站点整改:很多老站的上传目录从未配置执行限制,是入侵重放率最高的入口之一。
前置条件
- Nginx 1.10+,PHP 通过 PHP-FPM 以 fastcgi 方式对接(本方案核心即拦截 fastcgi 请求)
- 明确站点的上传目录路径,如 /www/wwwroot/site/upload、/uploads、/static/attach 等
- 具备 Nginx 主配置或站点 vhost 配置的修改与 reload 权限
原理说明
PHP 文件能否执行,取决于 Nginx 是否把请求交给 fastcgi_pass 后端的 PHP-FPM 处理。WebShell 要发挥作用,必须经历”写入 + 执行”两步,上传目录禁执行就是在第二步设卡:利用 Nginx 的 location 正则匹配,对上传目录下所有 .php、.php5、.phtml 等可执行扩展名的请求直接返回 403 或 404,请求根本到不了 PHP-FPM。这里有一个关键陷阱:Nginx 的 location 匹配中,带 ^~ 前缀的前缀匹配优先于正则匹配,如果上传目录的静态资源 location 写法不当,正则拦截规则会被跳过;同理,若正则写在 PHP 通用解析规则 location ~ \.php$ 之后,也会因顺序问题失效。因此配置顺序和前缀符号必须严格按本文写法。
操作步骤
第一步:在站点 server 块中添加拦截规则
server {
listen 80;
server_name example.com;
root /www/wwwroot/site;
# 上传目录禁止执行脚本(必须放在通用 PHP 解析规则之前)
location ~* ^/(uploads|upload|static/attach)/.*\.(php|php5|php7|phtml|pht)$ {
deny all;
# 也可改为 return 404; 避免向攻击者暴露"这里被特殊防护"
}
# 上传目录整体禁用执行权限的另一种写法:匹配目录后拒绝 PHP
location ~* ^/uploads/.*\.php$ {
return 404;
}
# 通用 PHP 解析规则保持不变,放在上述规则之后
location ~ \.php$ {
fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_index index.php;
include fastcgi.conf;
}
}
关键点:deny all 或 return 404 必须写在独立的 location 块中,且该块要先于 location ~ \.php$ 被 Nginx 评估——同为正则 location 时按出现顺序取第一个命中。
第二步:多站点批量加固
站点较多时,把拦截规则抽成公共片段文件统一引入:
# /etc/nginx/snippets/deny-php-upload.conf
location ~* ^/(uploads|upload|images|attach)/.*\.(php|phtml|pht)$ {
deny all;
}
# 各站点 vhost 中在 PHP 解析规则之前插入一行
include snippets/deny-php-upload.conf;
第三步:配合写入层收敛风险
配置层禁执行是兜底,写入层同步收紧效果更佳:上传接口强制重命名文件为随机名并去掉原始扩展名;上传目录属主设为 www 用户且不给执行位(chmod -R 755 uploads && find uploads -type f -exec chmod 644 {} \;);PHP-FPM 的 open_basedir 限定站点根目录之外不得访问。
配置验证
先做配置语法检查与平滑重载:
nginx -t && nginx -s reload
然后实测拦截效果:在上传目录创建一个测试文件 echo '<?php echo "executed"; ?>' > /www/wwwroot/site/uploads/test.php,浏览器或 curl 访问 http://example.com/uploads/test.php,应返回 403(或 404),且页面中不出现 executed 字样。再访问上传目录内一张正常图片,应返回 200,确认静态资源不受影响。最后检查主站功能正常:访问任一动态页面确认 PHP 解析未被误伤。
常见问题
FAQ 1:配置了拦截规则但 WebShell 依然能执行?常见原因有三种:一是正则只匹配了 /uploads 开头,而站点真实上传路径是 /data/attach 等未覆盖的别名路径,需用 nginx -T 导出全部配置核对路径;二是存在嵌套 location,内层 location ~ \.php$ 抢先命中,把内层也加上同样的 deny 逻辑即可;三是攻击者上传了 .user.ini 或 .htaccess 实现目录级劫持,需在 Nginx 层同时禁用这些文件: location ~ (\.user\.ini|\.htaccess)$ { deny all; }。
FAQ 2:禁执行后上传目录里的合法 PHP 报 403 了?说明该目录确实存在需要执行的合法脚本,这本身就是反模式。正确做法是把可执行 PHP 从上传目录迁出到程序目录,仅保留纯静态资源;若短期内无法迁移,可将该子路径加白:在 deny 块之前为该精确子目录配置正常解析 location,并通过白名单文件比对限制可执行文件清单,避免留下无差别执行口子。
总结
上传目录禁执行是性价比最高的站点加固措施之一:一段正则就能把”文件写入”与”代码执行”两条链路切断,让上传漏洞从致命降级为低危。落地时注意三点:拦截 location 必须位于通用 PHP 解析规则之前,路径要覆盖全部上传别名目录,配置后必须用真实 PHP 文件实测验证。建议将此规则纳入站点部署模板,配合定期扫描上传目录中的可疑脚本,形成防护闭环。