Magento 1 after end of life: SUPEE-11346, the CVE-2020-9664 cliff, and the OpenMage bridge
Magekwik Security 6 min read
Magento 1 hit end of life on 30 June 2020 after SUPEE-11346. Every flaw since — starting with the 9.8-rated CVE-2020-9664 — has no vendor patch. What Cardbleed exploited, how to check a store, and why OpenMage LTS is the realistic bridge.
What it is
Magento 1 (Community and Commerce) reached end of life on 30 June 2020. Adobe shipped one final security patch, SUPEE-11346, on 22 June 2020 and then stopped. Every vulnerability disclosed against the 1.x codebase since that date has no vendor fix. The install base did not evaporate on the EOL date — tens of thousands of storefronts still serve checkout on unpatched 1.9.x/1.14.x today, which makes "runs Magento 1" a reliable proxy for "has an unremediated remote-code-execution path."
SUPEE-11346 itself closed two issues that define the risk profile of an abandoned platform: CVE-2020-9664 (PHP object injection leading to arbitrary code execution) and CVE-2020-9665 (stored XSS). The first is the one that matters.
The cliff
Root cause
The structural problem is not a single bug; it is a frozen dependency tree. Magento 1 ships a bundled, forked copy of Zend Framework 1 (itself EOL since 2016), Prototype.js, and a large surface of admin controllers (downloader/, Magento Connect, RSS, import/export) that were written before modern deserialization and SSRF hardening. PHP object injection flaws like CVE-2020-9664 exist because untrusted input reaches unserialize() against classes whose __destruct()/__wakeup() methods perform filesystem or database side effects — the classic POP-chain gadget pattern.
Once the codebase is EOL, each newly found gadget or auth-bypass is permanent. There is no backport, no CVSS re-scoring, no advisory. The only parties writing fixes are the community and the attackers, and the attackers move first because a version string on a dead platform is a guaranteed, non-expiring lead.
How attackers abuse it
The pattern is consistent across the Magento 1 skimming campaigns. An unauthenticated RCE or admin-auth bypass gives a foothold; the attacker then uses legitimate platform machinery to persist and to place a card skimmer on the checkout DOM.
- Reach an object-injection or upload sink to execute code without credentials.
- Use the
Magento Connect/downloader/subsystem to install a PHP backdoor (Sansec observed amysql.phpdropper) so access survives the initial patchless entry point being closed. - Inject JavaScript into a bundled asset such as
prototype.js, or into the CMS/layout, so the skimmer loads on every payment page and exfiltrates PAN + CVV to an attacker host.
Because the skimmer rides an existing, trusted script rather than an external <script> tag, naive CSP allowlists and manual "view source" checks miss it.
Who got hit / real examples
In the weekend of 11-13 September 2020 — ten weeks after EOL — Sansec documented the Cardbleed campaign, the largest automated Magecart operation recorded at the time. It infected roughly 1,900 distinct Magento 1 stores in the first days and climbed to 2,806, about 3% of the entire Magento 1 install base. The intrusions abused the /downloader endpoint and Magento Connect to drop mysql.php and patch prototype.js with skimmer code. Sansec linked the timing to a Magento 1 RCE zero-day advertised for sale by an actor using the handle z3r0day shortly before the campaign.
Not a one-off
How to check
Confirm the version and whether SUPEE-11346 is present, then look for the persistence artifacts.
# Version string — load Mage.php, then ask it (getVersion() is self-contained)
php -r "require 'app/Mage.php'; echo Mage::getVersion(), PHP_EOL;"
# or read the version parts straight from the source:
grep -A8 "getVersionInfo" app/Mage.php | grep -E "major|minor|patch|revision"
# Was the final patch applied? SUPEE-11346 touches these files
grep -l "SUPEE-11346" app/etc/applied.patches.list 2>/dev/null
# Unexpected droppers in webroot
find . -name "mysql.php" -o -name "*.php" -newermt "2020-06-30" \
| xargs grep -l "eval\|base64_decode\|assert(" 2>/dev/null
# Tampered bundled asset — compare against a pristine copy
git diff --stat js/prototype/prototype.js 2>/dev/null
grep -RiE "onestepcheckout|guaranteed|atob\(|fromCharCode" skin/ js/ | head
Also review admin user list, secret admin path, and outbound connections from the checkout template. An externally reachable downloader/ is itself a finding.
How to fix it
There is no supported patch stream for Magento 1. Two defensible paths exist; picking neither is the failure mode.
- Migrate to Adobe Commerce / Magento 2.4.x. The correct long-term answer, but a project, not a patch. It does not help the store that is exposed this week.
- Move to
OpenMage LTS. The community fork of Magento 1 Community Edition, actively maintained on the 19.x/20.x lines, is the pragmatic bridge. It backports fixes the abandoned codebase never received — e.g. custom-layout RCE hardening in 19.4.22/20.0.19, arbitrary-upload fixes in 19.4.15/20.0.13, and CVE-2023-41879 (weak 6-hex guest-orderprotect_code, CVSS 7.5) fixed in 19.5.1/20.1.1. Migration is largely composer-driven and preserves the extension architecture.
composer require openmage/magento-lts:^20.1
# then diff local core mods against upstream, re-run compile, retest checkout
Whichever path you choose, treat any current Magento 1 install as breached-until-proven-clean: remove the downloader/ subsystem from the public webroot, rotate admin credentials and encryption keys, verify integrity of all bundled JS, and confirm no unknown admin users. A clean scan of a dead platform is a snapshot, not a guarantee — the only durable remediation is getting off the EOL codebase.
References & sources
Primary sources — advisories, vendor research, and the CVE record. Verify against these, not us.