首页 / 新闻资讯 / 技术教程

上传目录禁止执行 PHP 的两种写法

2026-04-05分类:技术教程顺云技术组

上传目录一旦能执行 PHP,等于把一块服务器执行权交了出去,攻击者把脚本伪装成图片传上来,就能直接在服务器上运行代码,后果远不止页面被篡改。程序侧的校验再严,Web 层这道兜底也必须有,校验逻辑可能有漏洞,拦截规则是死的,不会失手。

为什么正则要写全变体

绕过上传校验的常见手法不少:双写后缀、大小写变换、利用老版本 PHP 的截断符,最终落地的文件可能是 .php5、.phtml、.Php 这类变体。所以 Web 层拦截的正则要把常见 PHP 变体一网打尽,并且不分大小写。

Nginx 写法一:正则拒绝

在站点 server 块加:

location ~* ^/uploads/.*\.(php|php5|php7|phtml)$ {
deny all;
}

命中 uploads 下的 PHP 类请求一律拒绝,返回 403。~* 修饰符本身已忽略大小写。注意正则 location 之间的匹配顺序,这条配置所在的位置要能被请求命中,改完 nginx -t 加 reload。

Nginx 写法二:整目录收口

更稳的做法是把 uploads 整个目录限定为纯静态:

location ^~ /uploads/ {
location ~ \.php$ { return 403; }
expires 7d;
}

^~ 前缀匹配优先于正则,先把 uploads 圈进独立范围,内层再拦 .php,顺带给静态文件加个过期时间。两种写法选一种即可,同时存在时留意嵌套关系,别互相抢匹配。更进一层的隔离是让上传走独立域名或对象存储,主域根本不代理上传目录,执行面直接消失,有条件实现这个形态的业务优先这么做。

Apache 的处理

Apache 2.4 写在站点配置或 .htaccess:

<Directory /var/www/site/uploads>
<FilesMatch "\.(php|phtml)$">
Require all denied
</FilesMatch>
</Directory>

Nginx 反代加 Apache 后端的混合架构两条都要配,只配前面一层,请求绕过反代直连后端时就没防住了。

验证是否生效

往 uploads 目录放一个内容为 <?php echo 1; 的 test.php,浏览器访问 https://站点/uploads/test.php,返回 403 才算过关;输出 1 或提示下载,都说明没生效,回头检查 location 是否被其他正则抢先匹配。用 curl -I 看状态码比肉眼确认更快,http 和 https 两个版本都要查,两边配置相互独立。程序侧的后缀白名单校验、文件随机重命名照常做,Web 层兜底和程序校验两层都在,这条线才算完整。

技术教程