Skip to content

Why a hacked Joomla site keeps getting reinfected

The redirect to a pharmacy site was removed, the infected files were deleted, a security extension was installed, and ten days later the same thing is back. This pattern is so common that it is worth describing on its own. The first cleanup removed what the attack did and left the attacker's way in untouched.

Someone who has had access to a site for weeks rarely relies on a single door. Cleaning the visible damage leaves the others open.

The visible infection is the last thing that was done

What you notice, spam pages, a redirect, a warning from the hosting company, is the payload. It was placed through some access route, and that route was usually set up earlier and somewhere else. A scanner that finds and deletes the payload files reports the site as clean, and by its own definition it is right.

So the question during a cleanup is where the attacker can still get in. These are the places we go through.

Extra files that run code

PHP files in folders that should only hold images and documents: images, media, tmp, cache and the logs folder. A file with a harmless name among two thousand photos is easy to miss by eye and easy to find with a search for .php files in those folders.

Do not trust file dates. Changing a file's date to match its neighbours is routine for an attacker, so "everything modified last Tuesday" finds the careless part of the attack only.

Changed files that were there all along

A few extra lines in the template's index.php, in a core file, or in an extension loaded on every page. The file belongs there, so nothing looks wrong in the folder listing.

Searching for these by hand does not scale. Comparing does. Download the exact same Joomla version from the official site and compare the whole tree against it, and do the same for each extension against its vendor package. Better still, replace rather than compare. Delete Joomla's core folders (administrator, api, cli, components, includes, language, layouts, libraries, modules, plugins) and put the clean ones from the package in their place, then reinstall each extension from a fresh download. Deleting matters. Copying clean files over an infected folder repairs the changed files and leaves every added file where it was. Third-party extensions and language packs live inside these folders too, so this is done on a copy first, with every extension package at hand. What is left to inspect by hand is small: the template, the images folder, configuration.php, .htaccess, and anything custom-built.

Server configuration files

.htaccess can redirect visitors on conditions, for example only people arriving from a search engine, and only on phones. The site owner, who types the address directly on a laptop, never sees it. This is why "it works fine for me" is not evidence.

A .user.ini or php.ini file in the site folder can tell PHP to load another file before every request. Unless you know the site needs one, its presence is a finding.

Accounts

Look at the list of Super Users and at every account with access to the administrator. An unfamiliar account is an obvious sign. A familiar account with a changed email address is a less obvious one, since a password reset then goes to the attacker.

From Joomla 4, users can also hold an API token, which allows access without logging in through the browser. Tokens should be reset for every privileged account during a cleanup, whether or not they look used.

Things that run on a schedule

Cron jobs in the hosting account, and from Joomla 4.1 the Scheduled Tasks screen in the administrator. A scheduled job that downloads a fresh copy of the malware every night makes every cleanup temporary. With command line access, php cli/joomla.php scheduler:list shows what Joomla itself runs. The hosting panel shows the server's cron jobs.

The database

Injected scripts inside article text or custom HTML modules, a rogue plugin registered in the extensions table, changed settings in an existing plugin. Restoring clean files does nothing about any of these.

The neighbours

On shared hosting, several sites often live in one account: the main site, a forgotten staging copy in a subfolder, an abandoned WordPress blog from 2016. They can all write to each other's files. Cleaning one while another stays infected or outdated is the most frequent reason for reinfection we know of. Every site in the account is either cleaned and updated or removed.

Old backup archives and installer scripts left in the web folder belong to the same family. A full site backup that anyone can download contains the database password.

The original hole

Everything above is about ways back in. There is also the way in. Typically it is an outdated extension with a published vulnerability, an outdated Joomla core, or a stolen password for the administrator, FTP or the hosting panel.

The server's access logs usually show it, if they go back far enough: requests sent to a file that has no reason to receive them, shortly before the first malicious file appeared. Hosts often keep logs for only a few weeks, which is one more reason not to wait. If the cause is never identified, the safe assumption is that everything outdated was the cause, and all of it gets updated or removed.

The order that works

  1. Take a full copy of the infected site, files and database, and keep it off the server. It is the evidence, and it is the fallback if the cleanup breaks something.
  2. Contain it. Put the site offline or restrict access to your own IP address while work is in progress. Remove accounts nobody recognises, log out all sessions, and change the hosting panel password straight away. That one is not stored anywhere on the site, so changing it early gives nothing away.
  3. Replace core and extensions from clean packages, then go through what remains by hand, then the database, accounts, scheduled jobs and the other sites in the account.
  4. Update everything to a supported version. Remove what is not used.
  5. Now change every remaining password: Super Users, database, FTP or SFTP, API tokens, and the email accounts used for password resets. We do this after the cleanup and not before. The database password is written in configuration.php, so a backdoor that is still present reads the new one the moment it is saved.
  6. Watch the file system and the logs for the next weeks. If the hosting company or Google flagged the site, request a review once it is clean.

Restoring a backup is a legitimate shortcut if the backup is older than the break-in. It brings back the original hole as well, so steps 4 and 5 still apply, immediately.

What nobody can promise

No cleanup comes with a guarantee that nothing was missed, and anyone offering one should be asked how they would know. What can be offered is a method: establish what happened from the evidence, rebuild from known clean sources, close the entry point, and watch.

That is how our hacked website recovery work is organised, starting with an assessment of what happened before any cleanup is quoted. Sites that are updated on a schedule rarely need it, which is the unglamorous argument for a maintenance plan.

Tell us what you’re working with.

Send the site address and what’s bothering you.

Let’s talk