
Docker-based CTF lab demonstrating CVE-2024-4577 PHP-CGI argument injection leading to RCE. Includes vulnerable PHP 5.4.1 CGI, exploit scripts, and flag retrieval.
A self-contained, from-scratch Docker lab that demonstrates the real
argument-injection → Remote Code Execution primitive behind CVE-2024-4577.
Exploit it over http://localhost:8080, pop a shell, and read the flag from
inside the container.
This is a genuine vulnerability, not a simulated one. The container compiles an unpatched PHP 5.4.1 CGI binary and wires it behind Apache exactly the way real vulnerable hosts are configured. There is no fake "if password == flag" check anywhere — the only way to get the flag is to actually achieve code execution.
CVE-2024-4577’s signature payload uses %AD (a soft hyphen). That trick
only works on Windows, because Windows’ "Best-Fit" character encoding
converts the byte 0xAD into a real - (0x2D) after PHP’s CVE-2012-1823
patch has already checked the query string. That encoding conversion is done by
Windows itself — it does not happen inside a normal Linux Docker
container.
So a faithful, runnable-on-your-machine Linux lab reproduces the exact same
RCE primitive — php-cgi option injection through the URL → -d auto_prepend_file=php://input → code execution — using the literal -
form (which is CVE-2024-4577’s parent bug, CVE-2012-1823). The only
difference from a real Windows CVE-2024-4577 target is the %AD→-
encoding-bypass layer, which this README documents in full (see
How it works and
poc.http).
| This Linux lab | Real CVE-2024-4577 (Windows) | |
|---|---|---|
| Vulnerable component | php-cgi | php-cgi |
| RCE primitive | -d auto_prepend_file=php://input | identical |
| Delimiter in URL | literal - | %AD (best-fit → -) |
| Bypasses 2012 patch? | N/A (PHP predates the patch) | Yes, via Windows best-fit |
| Runs on Win 11 Home + Docker Desktop | ✅ | ❌ (needs Windows containers) |
If you specifically need the bit-for-bit Windows %AD reproduction, you need a
Windows-container-capable Docker host (Windows Server / Win Pro + Hyper-V) — it
will not run on Windows 11 Home. The Windows payload is included in
poc.http for reference.
CVE-2024-4577 — PHP CGI Argument Injection leading to Remote Code Execution. Discovered by DEVCORE (Orange Tsai / Angelboy), disclosed 2024-06-06.
When PHP is deployed in CGI mode (or the php-cgi.exe binary is otherwise
reachable) on Windows with certain system locales (Traditional/Simplified
Chinese, Japanese, and others), the web server passes the HTTP query string to
php-cgi as command-line arguments. An attacker can smuggle php-cgi
command-line options (-d ...) into that query string. Windows’ best-fit
codepage conversion turns the soft-hyphen byte 0xAD (%AD) into an ASCII
hyphen -, which slips past the CVE-2012-1823 hardening and lets the attacker
set arbitrary PHP INI directives — most usefully
auto_prepend_file=php://input with allow_url_include=1, which executes the
attacker-supplied request body as PHP. Result: unauthenticated remote code
execution. It was weaponized in the wild within days (e.g. TellYouThePass
ransomware).
CGI passes the query string as argv. Per RFC 3875, if a CGI request’s
query string contains no unencoded =, the server splits it on +,
URL-decodes each word, and passes the words to the CGI program as
command-line arguments. php-cgi therefore receives attacker-controlled
argv.
php-cgi parses those argv as options. Historically php-cgi would
interpret -d key=value, -T, etc. from that argv. Feeding
-d allow_url_include=1 -d auto_prepend_file=php://input makes PHP execute
the request body as code → CVE-2012-1823.
The CVE-2012-1823 fix is incomplete on Windows. The 2012 patch added a
guard in sapi/cgi/cgi_main.c: roughly "if the (raw) query string begins
with - and has no =, skip option parsing (skip_getopt)." An attacker
sending a literal - is now blocked.
Best-fit encoding defeats the guard (the 2024 bug). On Windows, PHP
converts the command line using the locale codepage with best-fit mapping
enabled. The attacker sends %AD (byte 0xAD, soft hyphen). At the
moment of the guard’s check the first byte is 0xAD, not -, so
skip_getopt is not set. Later, when PHP actually builds the argv,
Windows best-fit-maps 0xAD → -, so getopt now sees -d. The option
injection fires after the check that was supposed to stop it. That
check-then-convert ordering is the entire vulnerability.
In this Linux lab, steps 1–2 are reproduced exactly with a php-cgi that predates step 3’s patch, so the literal
-form works and demonstrates the identical RCE. Step 4 is the Windows-only layer, documented but not executed (Linux has no best-fit conversion).
Fixed in 8.3.8, 8.2.20, 8.1.29. Therefore vulnerable:
Conditions: Windows OS; PHP running as CGI or php-cgi.exe exposed
(the default XAMPP on Windows configuration is vulnerable); an affected
locale for the best-fit path. (The parent bug CVE-2012-1823 — the primitive
this lab runs — affects any OS running a pre-2012-fix php-cgi in this
configuration.)
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
curl for exploitation (curl.exe is built into Windows 10/11; also in
Git Bash / WSL / macOS / Linux).Open a terminal in the cve-2024-4577-lab/ folder.
docker compose up --build -d
docker build command:
docker build -t cve-2024-4577-lab .
docker run command:
docker run --rm -d -p 8080:80 --name cve-2024-4577-lab cve-2024-4577-lab
The build compiles PHP from source (~2–5 min the first time). If it fails on your machine (offline, no toolchain, museum.php.net blocked), use the fallback base image:
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
You should see the ACME Internal Status Portal HTML, and crucially:
<li>PHP version: <code>5.4.1</code></li>
<li>SAPI: <code>cgi-fcgi</code></li>