PatchDay Alert
Analysis · 5 min read · 1,000 words By analysis-desk

A soft hyphen reopened a bug PHP closed in 2012

CVE-2024-4577 is a patch bypass of a 12-year-old PHP-CGI flaw. The 2012 fix sanitized the input. Windows then helpfully rewrote a soft hyphen back into a real one, after the check, and handed the attacker their command-line argument anyway.

A soft hyphen reopened a bug PHP closed in 2012

The 2012 version of this bug, CVE-2012-1823, let an attacker smuggle command-line arguments into PHP running as CGI by abusing how query strings without an = sign got passed to the php-cgi binary. PHP fixed it. The fix checked the input and rejected the dangerous patterns. For twelve years, that was the end of it.

CVE-2024-4577 is the same attack walking back through a door the fix didn’t know was there. Orange Tsai of DEVCORE, who titled the writeup “Make PHP-CGI Argument Injection Great Again,” noticed that Windows performs a “Best-Fit” character conversion when mapping Unicode to certain legacy code pages. A soft hyphen, 0xAD, isn’t a regular hyphen and sails past PHP’s sanitization. But when the string crosses into the Windows code-page layer, Best-Fit helpfully converts that soft hyphen into a plain -. The check ran on the safe version of the input. The dangerous version is what php-cgi actually received. The attacker gets to inject -d options like allow_url_include=1 and auto_prepend_file=php://input, and that’s unauthenticated remote code execution, CVSS 9.8.

Why this is the interesting failure

Most patch bypasses are about a fix that was too narrow, one code path closed while a sibling stayed open. This one is more instructive, because the 2012 fix was correct for the input it inspected. It failed because the input changed after it was inspected. The sanitization and the consumption of the data were separated by an encoding transformation nobody in the loop accounted for.

That’s a general lesson worth carrying past PHP. Any time you validate a string and then hand it to a downstream component that re-encodes, normalizes, or “helpfully” rewrites it, your validation is checking a value that no longer exists by the time it matters. Unicode normalization, locale code-page mapping, URL decoding that happens twice, a library that trims or substitutes characters: each is a place where “I already sanitized this” quietly becomes false. The defensive posture the bug argues for is to validate at the point of use, against the exact bytes the sensitive function will see, not at the point of entry against a version that’s still going to be transformed.

The exposure is wider and narrower than it looks

Narrower, because this only bites a specific configuration: PHP running in CGI mode on Windows, with a vulnerable code page. The conditions DEVCORE confirmed as default-exploitable were Windows systems set to Traditional Chinese (code page 950), Simplified Chinese (936), or Japanese (932) locales. If you run PHP via PHP-FPM, or mod_php, or on Linux, this specific vector doesn’t apply, and plenty of modern stacks moved off php-cgi years ago.

Wider, because of one product: XAMPP. The popular Windows AMP bundle ships configurations that expose the PHP executable in the CGI directory by default, which means a lot of Windows machines are running vulnerable php-cgi without anyone having made a deliberate decision to. Those are exactly the boxes that don’t get tracked: developer laptops, internal test servers, a forgotten demo instance, an appliance that embedded XAMPP. Because the PHP team couldn’t guarantee the safety of every locale and configuration, the official guidance and the fix treated Windows php-cgi as broadly at risk rather than carving out “safe” locales. Assume you’re exposed if the configuration exists, rather than betting your locale isn’t on the list.

It was weaponized in roughly two days

This is the part that removes any room for a slow response. PHP disclosed the bug and shipped fixed builds on June 6, 2024. By June 8, defenders were already seeing exploitation, and within about 48 hours of disclosure the flaw was folded into the TellYouThePass ransomware campaign. CISA added it to the Known Exploited Vulnerabilities catalog on June 12 with a July 3 deadline and the ransomware flag set. The TellYouThePass operators used the RCE to invoke mshta.exe against an attacker-hosted HTA file, running VBScript that loaded a .NET build of the ransomware straight into memory. Public proof-of-concept exploits, including one from watchTowr, were available almost immediately, which is what turns a clever encoding trick into a commodity mass-exploitation event. Exploitation continued well into 2025, broadening past the initially-targeted regions.

What to do

  • Patch to PHP 8.3.8, 8.2.20, or 8.1.29 or later. Those builds fix the Best-Fit handling. If your PHP rides inside another product (XAMPP, a vendor appliance, a packaged web app), update through that vendor and confirm the bundled PHP version, because that’s where the unpatched copies hide.
  • Find your php-cgi on Windows. The real risk is the instances you don’t know about. Inventory Windows hosts for XAMPP and any Apache configuration that maps requests to php-cgi.exe. A forgotten dev box running XAMPP is the classic victim here.
  • If you can’t patch immediately, get off CGI mode. Migrating to PHP-FPM or mod_php, or blocking access to the CGI endpoint, removes the vector. CGI-mode PHP is a legacy deployment style with little reason to persist.
  • Hunt for the post-exploitation pattern. Web server processes spawning mshta.exe, cmd.exe, or powershell.exe, and php-cgi requests containing encoded soft hyphens or auto_prepend_file/allow_url_include parameters, are exploitation indicators.

The reframe is the bit to keep. A patch is only as good as its assumption that the data it inspected is the data that gets used. CVE-2024-4577 is twelve years of safety undone by an operating system silently rewriting one character downstream of the check. When you validate input, validate it where it’s consumed, against what the consumer actually sees, because everything in between is allowed to change it. We track the patch-bypass entries in the KEV catalog with particular attention, because a bypass means a control someone already trusted has quietly stopped working.

Sources

Share

Related field notes

One email, every weekday morning.

You're in. Check your inbox.

Get the digest

Free. Weekday mornings. Plain English CVE triage.

Check your inbox to confirm.