In our forensic walkthrough of a Magecart-style attack we made a point that, with hindsight, deserved its own article: cleaning the malware off a store is not the same as fixing the store. We removed the skimmer, the web shells and the SUID-root binaries from that PrestaShop 1.7.x shop — and shortly afterwards it was hit again. Not through a backdoor we missed, but through the front door: an application-level vulnerability the cleanup had contained but never closed. This post is the playbook we wish we had applied the first time — how to harden a PrestaShop store you cannot upgrade yet, so that removing the payload also closes the hole it came through.
If you can upgrade to PrestaShop 8 or 9 today, do that instead — it is the only durable fix, and everything below is a stopgap. But for a real store, an upgrade is a multi-week project: module compatibility, theme refactoring, payment rewiring. The interim is real, and during it you are a live target. This is the advanced, incident-grade companion to two foundational guides — start with the PrestaShop Security Hardening Checklist for the full baseline, or the plain-English guide for store owners if you're not technical — then come back here for the layer that keeps an un-upgradable store survivable.
1. Find the entry vector, not just the payload
The single most expensive mistake in incident response is treating the visible symptom as the wound. A skimmer in your theme JavaScript is the symptom. The wound is whatever let an attacker write that JavaScript in the first place — a web shell, an unpatched module, an injection endpoint. Remove the skimmer and leave the wound open, and you have bought yourself days, not safety.
Before you harden anything, answer one question with evidence: how did code get written to disk? Read the access logs around the file-creation timestamps of the malware — use the inode change time (ctime), not the modification time (mtime), because attackers routinely back-date mtime to hide. Correlate suspicious POST requests, unusual query parameters and response-size anomalies with the moment the payload appeared. If you cannot name the entry vector, assume it is still open and treat every step below as load-bearing.
2. Virtual-patch the vulnerabilities your logs and advisories actually name
PrestaShop 1.7.x stores have been hit by a number of weaponised vulnerabilities that automated scanners probe for. If you cannot upgrade, you patch the specific code paths directly — a "virtual patch" applied to your own files. These are stopgaps, so track the upstream advisories, but they close the doors that are actively being kicked in. Start from evidence: identify the exact vulnerable module or core version named by your logs and the relevant advisories, and patch that. A known 2022 attack chain combined any exploitable SQL injection with Smarty MySQL-cache execution to turn database access into running PHP — so the pattern to watch for is an injection that reaches your database paired with a cache flaw that executes code. The faceted-search example below is one such injection vector, but only patch the installed ps_facetedsearch version if it is confirmed vulnerable; the SQL-injection half of that chain can equally come from other core or module code.
2.1 The faceted-search SQL injection (if your installed version is vulnerable)
Some older builds of the ps_facetedsearch module concatenate filter values straight into a SQL WHERE clause — a blind SQL-injection primitive. Confirm against the advisories that the version you are running is actually affected before touching it; not every 1.7.x store carries this exact flaw. Where it does apply, the fix is to run every request-derived value through PrestaShop's own escaping before it reaches the query: string values wrapped in pSQL() and quoted, numeric ranges hard-cast with (float). The vulnerable pattern appends current($values) raw into the condition; the patched pattern appends pSQL(current($values)) in quotes for strings and a cast value for numbers. If this version is confirmed vulnerable, this is the lock on the front door. (The same module under crawler load is also a denial-of-service risk; we cover that ?q= flood angle separately.)
2.2 The Smarty MySQL-cache RCE (CVE-2022-36408)
This is the half of the chain that turns database access into code execution: the database-backed Smarty template cache can be coerced into rendering attacker-controlled PHP. Most stores never use it. Disable it outright in config/smarty.config.inc.php by neutralising the conditional that loads SmartyCacheResourceMysql.php and sets the caching type to mysql — wrap that include in a permanently-false condition so it can never be reached. Then confirm the store is actually running filesystem caching afterwards (the PS_SMARTY_CACHING_TYPE setting should read filesystem, found under Advanced Parameters → Performance). An SQL injection alone is serious; an SQL injection that can write a PHP template into a cache and execute it is a full takeover. Close both halves and the chain is broken.
2.3 Abandoned modules and the files attackers re-target
Old, unmaintained modules (the RevSlider family is the classic example) are a recurring vector. The durable fix is to uninstall and replace the module — do that wherever you can. If you genuinely cannot remove one because the theme depends on it, do not blanket-deny direct PHP across the whole module folder: many modules legitimately expose direct entry points — AJAX scripts, cron URLs, payment callbacks, legacy validation scripts — and a Require all denied over every .php will break them. Instead, audit the module's routes and endpoints first, then block only the specific known-abandoned entry points an attacker re-targets, by adding a scoped .htaccess rule (Require all denied) for those individual files. Then lock the static entry points an attacker re-targets after every cleanup — on filesystems that honour extended attributes (ext4, xfs, btrfs), make index.php and your config files immutable with chattr +i. A future shell cannot overwrite an immutable file without first running chattr -i, which needs root it shouldn't have. For the full set of root-directory and admin-folder rules this builds on, see our .htaccess security and performance rules.
3. Lock the PHP runtime — correctly
Disabling the PHP functions a shell uses to spawn operating-system processes is worth doing, but it is the step most often done wrong, in a way that looks applied but is not. The blocklist you want covers the process-spawning family: exec, system, shell_exec, passthru, popen, proc_open and its relatives, plus dl, show_source and phpinfo. Setting it is the easy part. Three things people miss are what decide whether it does anything:
- disable_functions is PHP_INI_SYSTEM. It is only enforced from the startup php.ini of the exact SAPI that serves your site. On a panel-managed box (Plesk, cPanel, CloudPanel) the web SAPI is usually a specific PHP-FPM pool or a per-version Apache instance — not the system CLI PHP you reach first from the shell. Edit the right php.ini and restart that exact service, or your change does nothing while looking done.
- Check disable_functions from the right SAPI. ini_get('disable_functions') run from CLI may not match what the web SAPI actually enforces — the CLI PHP and your web PHP-FPM pool can carry different configs. The only trustworthy test is a real one, executed through the web: a temporary script with a random name, deleted immediately, that calls function_exists('exec') and attempts an actual disabled call such as @exec('id'), fetched over HTTP. Trust the result of the attempt, not a config value read from the wrong SAPI.
- It is not a kill switch. It stops a PHP shell from shelling out to the OS. It does not neutralise a PHP eval shell, which can still read and write files inside open_basedir and talk to your database with the credentials sitting in app/config/parameters.php. Treat it as one layer, not the answer — which is exactly why detection (step 5) and the WAF (step 4) sit alongside it, not instead of it.
One compatibility note so you don't break uploads: PrestaShop core touches exec() in a couple of image and MIME-detection paths, but those have finfo fallbacks, so disabling it does not break product-image uploads. Test image uploads on staging before applying to live anyway.
4. Put Cloudflare in front — as a virtual-patch layer
A reverse-proxy WAF is the closest thing to a virtual patch you can deploy in minutes: it inspects requests before they ever reach your vulnerable code. But it only earns that role if attackers cannot route around it.
4.1 Lock the origin to Cloudflare
A WAF in front of an origin that still answers on its public IP is optional from the attacker's perspective — they just hit the IP directly and skip the proxy. Restrict the server so only Cloudflare can reach ports 80 and 443: an iptables chain that accepts Cloudflare's published IPv4 and IPv6 ranges (and localhost) on those ports and drops everything else. The catch most people miss: your mail DNS leaks the origin IP. An MX record, an SPF entry, or a grey-clouded mail. subdomain hands the attacker your real address — and the origin firewall is what makes that leak harmless for the web. So proxy every web record through Cloudflare (orange cloud), and firewall the origin regardless. Leave SSH, mail and panel ports on their own access rules; the web lock-down should never touch them.
4.2 WAF custom rules — and the extension trap
On the Cloudflare Free plan you get five custom rules and no regular expressions — only contains, eq, starts_with, ends_with and lower(). That is enough for a focused rule set:
- Block web shells and executable uploads: known shell filenames, direct calls to abandoned-module PHP, and any PHP execution under asset directories (/img/, /upload/, /themes/, /js/, /cache/) where PHP should never run.
- Block injection payloads: path-traversal strings, requests for /etc/passwd, union select, information_schema, a literal opening PHP tag, base64_decode, and their common encoded variants.
- Block sensitive files: /.git/, /.env, composer.json and your config files — never serve them to the public.
- Block the worst nuisance crawlers by user-agent. If bad-bot traffic is a standing problem rather than a one-off, our guide to blocking bad bots and unwanted traffic goes deeper than five rules can.
The trap that bit us: Apache often executes more than .php. A typical AddType/AddHandler line maps .php3 .php4 .phtml to the PHP handler alongside .php. A rule that blocks .phtml and .phar but forgets .php3 and .php4 leaves a shell-upload door wide open — we found exactly that gap in our own rule set during review. Block every extension the server will actually run, and check your real AddType/AddHandler configuration rather than assuming the default.
4.3 Rate-limiting: know what your plan can and cannot do
Rate-limiting is the right tool against the crawler floods that hammer faceted search. But be precise about your tier, because the Free plan is far more limited than the docs imply at a glance. The table below is the distinction that decides whether your defence will actually fire:
| Capability | Cloudflare Free | Pro (Advanced Rate Limiting) |
|---|---|---|
| Match on URL path | Yes | Yes |
| Match on query string (e.g. ?q= facets) | No | Yes |
| Match on user-agent / source IP | No | Yes |
| Action | Block only | Managed challenge, block, log |
| Counting window | Fixed | Configurable |
The practical consequence: you cannot build a "throttle bots on ?q= faceted URLs" rule on Free, because the query string is invisible to it. The best Free gives you is a blunt per-IP flood limit on non-static paths. True query- and user-agent-aware rate-limiting, with a managed challenge instead of a hard block that would also bounce real customers, needs the Pro plan. For an actively-targeted store the faceted-search exposure usually justifies that upgrade — weigh it against the cost of the DoS it prevents.
5. Add detection — so round two is caught in hours, not days
Hardening reduces the odds; it does not make you immune. The difference between a bad afternoon and a disclosure-grade incident is how fast you notice. The store in our case study had been silently skimming customer card details for almost two days before anyone looked. Put eyes on it:
- File-integrity monitoring (AIDE, Wazuh, OSSEC, or even a simple hashed-manifest cron) over the webroot, alerting on any new or changed PHP file that didn't come from a deploy. New PHP appearing outside a release is the single highest-signal alarm you can wire up.
- Scheduled indicator sweeps for the patterns from real attacks — the indicators in our anatomy of a Magecart attack are a ready-made starting list — plus any new executable-extension file appearing in asset or upload directories.
- Cloudflare Security Events review on a schedule, not just after an incident. Your WAF is also a sensor, and it logs the attempts your rules blocked, which tells you what is being tried.
6. The admin panel is still a door — close it too
Everything above defends the public front end. Don't leave the back office unhardened on the assumption attackers only come through the catalogue: a leaked or brute-forced admin login skips your virtual patches entirely. On an un-upgradable store this matters more, not less, because the login form itself may be running old code. At minimum, rename the admin directory, force HTTPS everywhere (our SSL and HTTPS setup guide covers doing it cleanly), and add a second factor and a real password policy — the full treatment is in two-factor auth, password policies and admin security. Pair that with IP-level blocking of repeat offenders, which we walk through in IP bans for problem visitors.
7. The cure is still migration
Everything above is defense-in-depth on software that no longer receives security updates. It raises the cost of attack, closes the doors that are actively exploited, and buys you the weeks or months a real migration needs — but it is buying time, not safety. The store in our case study is exactly why: it was cleaned, and the same operator walked back in through an application flaw the cleanup had never patched. Plan the move to PrestaShop 8 or 9, on clean infrastructure, with rotated credentials, as the actual remediation. Treat the hardening here as what keeps the lights on until you get there.
And if you're reading this after a breach rather than before one, switch tracks: our data breach response guide covers what to do in the first hours, and the anatomy of the attack that motivated every step above shows you what you are actually up against.
Comments
No comments yet. Be the first!
Be the first to ask a question or share useful feedback.
Leave a comment
Share a question, an installation detail, or feedback that could help another reader.