Recovering a Magento 2.4.x store from the SessionReaper attack
How to confirm a SessionReaper/PolyShell compromise on Magento 2.4.x, remove the injected backdoors, restore wiped catalog data, and close the entry point.
- Magento 2
- Adobe Commerce
- Operations

You open the storefront and the categories are empty. Product pages 404. The admin still logs in, but the catalog grid shows zero rows, and your monitoring is firing on 500s from the frontend. If you are running Magento 2.4.x with file-based sessions and you have not applied the emergency patch from late 2025, the most likely explanation is not a botched deploy. It is the attack the community started calling SessionReaper, tracked as CVE-2025-54236 and patched in Adobe's APSB25-88 bulletin, usually paired with a PolyShell webshell dropped somewhere in your webroot.
The pattern is consistent enough to recognize on sight: catalog_product_entity (and often the linked EAV and index tables) truncated or dropped, plus one or more PHP backdoors — filefuns.php and a randomly-named file like a3d18861c4dd.php are the ones getting reported. The wipe is loud, but the backdoor is the part that will hurt you if you only fix what you can see. This walks through confirming it, containing it, restoring the catalog, and closing the door.
What SessionReaper actually does
CVE-2025-54236 is an unauthenticated input-validation flaw in the Commerce REST API. On stores using file-based session storage, a crafted request lets an attacker influence deserialized session data and, from there, reach code execution. That is the whole reason file sessions matter here — not because they are slow, but because they are the write primitive the exploit chains through.
Once the attacker has execution, the catalog wipe is almost a distraction. The valuable step for them is planting persistence: a webshell they can return to after you patch, plus quieter footholds in the database and cron. Treat the truncated catalog_product_entity table as the symptom that got your attention, not the incident. If you restore the catalog and skip the backdoor hunt, they come back through the same shell that afternoon.
Confirm the compromise before you touch anything
Do not start deleting files or rolling back yet. You want evidence first, both to scope the breach and because you may need it later. Pull your web server access logs and grep for POSTs to the REST API endpoints around the time the errors started:
grep -E 'POST .*/rest/(V1|all|default)/' access.log | grep -v ' 404 '
Look for unusual user agents hitting /rest/ with 200s, followed shortly by requests to files that should not exist. Then inventory PHP files that do not belong:
find pub/ app/ var/ generated/ -name '*.php' -newermt '2025-09-01' -type f
Any PHP under pub/media, var/, or generated/ is suspicious by default — those directories should not hold hand-written executable code. Note the modification timestamps; they anchor the rest of your investigation.
Find and remove every backdoor, not just the named two
filefuns.php and a3d18861c4dd.php are the reported filenames, but do not assume they are the only ones. Scan the whole tree for the shapes webshells take:
grep -rlE 'eval\(|base64_decode\(|assert\(|gzinflate\(|\$_(POST|GET|REQUEST)\[' pub/ app/ var/ generated/
Cross-reference the hits against your git history. On a store under version control, git status and git diff against a known-good tag will surface anything that was added or modified outside your deploys. Anything flagged that is not in your repo is a candidate for removal.
Then check the persistence layers a file scan misses. Look at the cron_schedule table and your crontab for jobs that shell out. Inspect admin_user for accounts you did not create and password resets you did not request. Check oauth_token and integration credentials, since REST access can be re-established through a rogue integration even after the shell is gone. Backdoors that live in the database survive a full file redeploy — that is the point of them.
Restore the catalog from a known-good backup, not from the live database
Once the store is clean, the wiped catalog has to come back. The instinct is to restore only catalog_product_entity and its related tables, but resist it. You do not actually know the attacker touched only that table, and product data is woven through EAV, inventory, pricing, and the index tables. A partial restore leaves you reconciling foreign keys by hand.
Restore the full database from the most recent backup you can trust from before the first suspicious log entry. If that backup is a week old, a week of lost orders is a smaller problem than a silently inconsistent catalog. After the import, rebuild everything:
php bin/magento maintenance:enable
php bin/magento setup:upgrade
php bin/magento indexer:reindex
php bin/magento cache:flush
Reconcile orders placed after the backup point separately, from your payment gateway records, rather than trying to merge database states.
Patch the entry point and harden sessions
A clean restore on an unpatched store is a countdown. Apply the APSB25-88 patch for your exact 2.4.x version before you bring the site out of maintenance mode — Adobe shipped an isolated hotfix precisely so you would not have to wait for a full version bump. Confirm it landed rather than assuming the composer step succeeded.
Then remove the precondition the exploit relied on. Move session storage off the filesystem to Redis in app/etc/env.php:
'session' => ['save' => 'redis', 'redis' => ['host' => '127.0.0.1', 'port' => '6379', 'database' => '2']],
While you are in there, rotate every credential the attacker could have read from env.php — database password, crypt key considerations, admin passwords, API tokens, and any payment gateway keys. Tighten permissions so the web user cannot write to code directories, and put a WAF rule in front of /rest/ if you have one available. This is the part we push hardest on with the stores we operate: the vulnerability gets patched once, but file-writable webroots and file-based sessions are the conditions that let a single bug become a full compromise, and they are worth fixing permanently.
What to do in the next hour
If any of the symptoms above match, put the store in maintenance mode now — before you finish reading logs. Containment first, forensics second, restore third, patch before you reopen. Then decide honestly whether you have a clean, dated backup and the time to run this end to end without missing a backdoor, because a half-cleaned store is worse than an obviously broken one.
If you are staring at a truncated catalog and are not certain you can find every foothold, that is a reasonable point to bring in help rather than guess. This is the kind of Magento 2 incident work we do — get in touch and we can walk the logs with you and scope a clean recovery.
Need this done on a real stack?
Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.
Contact us