A freelance web designer, working alone, kept three sites on one hosting account: two for clients and one for her own portfolio. The arrangement made sense on paper. One bill, one control panel, one login to remember, and a monthly saving of a few euros that mattered when work was uneven. This is a fictional composite, but the pattern is one that support teams see every few weeks.
One of the client sites, a small shop for a local ceramics studio, ran a plugin that had not been updated for most of a year. A known flaw in it was exploited by an automated script, and a malicious file landed in the site's folder. That would have been a one-site problem on any normal day. Because of how the account was arranged, it did not stay one.
She cleaned the ceramics site, checked it in a browser, and told the client it was sorted. A week later, all three sites were sending visitors to spam pages. This article follows what happened, why the first clean-up failed, and what she changed.
Why three sites behaved like one
On most shared hosting, an account is the unit of isolation. Everything inside it runs as the same system user. The web server process that serves site A can read and write the same files as the one serving site B, because to the operating system they are the same person. Add-on domains, subfolders and "sites" in the panel are labels, not walls.
So when a script on the ceramics site was hijacked, the attacker did not need a second exploit. A hijacked PHP script runs with the account's permissions, and the account's permissions covered three document roots. A short loop that walked up and across the directory tree was enough to find the other sites, and a short write operation was enough to add a line to their files.
The first week, in order
The sequence mattered, because the gap between the first clean-up and the second outbreak is where the lesson sits. Here is how it went.
| Day | What happened | What anyone could see |
|---|---|---|
| 0 | Automated script exploits the old plugin on the shop site and uploads a small PHP file. | Nothing. |
| 0-1 | The file scans the account, finds the other two folders, and drops copies of itself. | Nothing. Pages still load normally. |
| 2 | The client reports that a search result for the shop shows odd text in another language. | Search snippet only. |
| 2 | She finds injected code in the shop theme, deletes it, updates the plugin, changes the admin password. | The shop looks normal again. |
| 3-8 | Dormant copies in the other two folders re-infect the shop and activate on the others. | Intermittent redirects, mostly for visitors arriving from search. |
| 9 | All three sites redirect to spam. Two clients phone her on the same morning. | Everything. |
Note the redirects only fired for people arriving from search engines, and not for logged-in users. That is deliberate. Attackers hide the behaviour from the site owner, who types the address straight into the browser, and show it to strangers. It is why the designer could look at the sites and see nothing wrong.
Why cleaning the first site was not enough
Her clean-up was careful by the usual standard. She removed the injected code she could see, updated the plugin and changed passwords. What she did not do was ask where else the attacker's files might be. Three habits of this kind of malware explain the relapse.
First, it plants more than one door. The visible damage was a few lines at the top of a theme file. The door that let the attacker back in was a differently named PHP file buried in an uploads folder, with a name chosen to resemble a core WordPress file. Delete the visible damage and leave the door, and the damage returns within hours.
Second, it copies itself sideways. The account held two other document roots, so every copy dropped there was a fresh source of infection. Cleaning the shop while the portfolio still held a live copy meant the shop was reinfected from the portfolio, and the portfolio carried on being a source for the third site.
Third, it sets a timer. Some copies were written to wait, or to run only on specific requests, which is why the quiet days between the clean-up and the outbreak looked like success.
Finding the real way in
When she finally treated the whole account as one crime scene, the work was dull but quick. This is the order a support engineer would take, and you can follow the same steps on any account over SSH.
Start with what changed recently, across all three roots at once:
find ~/ -type f -name '*.php' -mtime -14 -not -path '*/cache/*' | sort
Then look for the usual fingerprints of injected code. These patterns are noisy, so read the hits rather than deleting them blindly:
grep -rIl --include='*.php' -E 'eval\(base64_decode|gzinflate\(|str_rot13\(' ~/
find ~/ -type f -path '*/uploads/*' -name '*.php'
PHP files inside an uploads folder are almost always wrong, since that folder is meant to hold images and documents. She found nine such files across the three sites. Next she checked the places attackers use to survive a clean-up: crontab -l for scheduled commands, each site's .htaccess for rewrite rules sending search visitors elsewhere, and the list of users in each WordPress dashboard for administrators she had never created. There was one, added on day zero.
The web server's access log gave the timeline. Searching it for the odd filename showed the first POST request to the planted file, from an address she did not recognise, hours before anything visible happened.
Separating the sites
Once the three sites were clean, the question was how to stop one failure becoming three. She moved each site to its own hosting account, with its own system user, its own database user and its own set of credentials. It cost a few more euros each month in total.
The alternatives are worth a short comparison, because "separate accounts" is not the only answer and not always the cheapest.
| Arrangement | Isolation between sites | Cost and effort |
|---|---|---|
| Many sites in one shared account | None at file level | Cheapest, least work |
| One shared account per site | Separate users and files | Modest cost, more logins |
| VPS with a user per site | Good if configured properly | You administer the server |
| Managed hosting, one site per container | Strong | Higher monthly cost, little admin |
She chose one shared account per site. Clients who wanted to pay for their own hosting were moved onto accounts in their own name, which also fixed an awkward question of who owned what.
Updating on a schedule
The plugin flaw was the cause, and it had been fixed upstream for months. Separation limits the damage; updates remove the cause. She set a recurring slot, the first Tuesday morning of each month, for the following:
- apply core, theme and plugin updates on each site, with a backup taken first;
- delete plugins and themes that are installed but not used, since inactive code can still be exploited;
- check the administrator user list and the last-modified times of PHP files;
- restore one backup to a test location, to confirm that it works.
Security-only updates for plugins with known flaws are applied the same week, not left for the monthly slot. She also turned on automatic minor updates where the client agreed, and wrote the update routine into her maintenance contract so that clients understood what they were paying for.
Verifying it
If you host more than one site under one account, ten minutes will show how exposed you are. In the control panel, look at whether each site has its own system user or whether they all share one. Over SSH, ls -l in each document root shows the owner; if it is the same name everywhere, a compromise in one is a compromise in all.
Then run the find command above on the whole account, not one site. Compare what you see with curl -sI -A "Mozilla/5.0" -e "https://www.google.com/" https://example.com/, which sends a search-style referrer and may reveal a redirect that a plain visit hides. The troubleshooting guide covers the rest of the sequence, and the status code reference explains what a 301 or 302 from an unexpected place means.
What stayed with them
Sites that share an account share their fate. Clean the whole account or none of it, find the door as well as the damage, and treat separation and updates as running costs of hosting other people's websites.