How to tell if your website was hacked months ago and nobody noticed
Found on 7 September, placed on 8 June
The reason anyone found out was a move. A European equipment-rental company needed its Joomla site shifted to a new host, so a backup got restored and someone looked at the file system for the first time in months. That's the only reason.
The earliest file timestamp we could pin down was 8 June 2026. The discovery was 7 September. Three months and one day, at minimum, with someone else's code sitting quietly on a live business site.
Nothing looked wrong from the outside. The site loaded. Orders came in. Nobody called.
What was actually on the server
When we went through the directory tree, here is what was there, in the order it turned up.
A webshell in the site root. One file, sitting next to index.php, with a name that looked like a legitimate Joomla system file. It wasn't.
Sixteen copies of an obfuscated loader, spread across the install. Not sixteen variants: sixteen literal copies of the same encoded file, placed in different directories. The encoding was a standard PHP base64/eval stack, the kind that passes a quick glance but breaks as soon as you actually decode it.
Ten uploader scripts inside the images folder. The images folder is writable by the web server so image uploads work. That also makes it the obvious place to put a file that accepts uploaded payloads. All ten files ended in .php.
The site's own index.php, rewritten. The original Joomla bootstrap file had been replaced with one containing 488 obfuscated goto statements. A goto-based obfuscation works by turning linear code into a jump table that's nearly unreadable by eye, even after you strip the encoding. The site loaded normally because the code eventually calls the real Joomla entry point; it just does other things first.
Zero-byte WordPress files on a Joomla install. Files with names like wp-login.php and paths that only make sense in a WordPress installation, all zero bytes, planted across the Joomla directory tree. They're probes: once a tool places them, it can check later whether they're still there and whether the environment changed. It also tells you the same toolkit had been through WordPress sites before it got here.
That's what three months of quiet access looked like.
Why the front of the site looked fine
The attacker's code is passive. It doesn't run on every page load. It sits and waits for an HTTP request that carries the right parameter, the right cookie, or the right header. When a normal visitor hits the homepage, the malicious index.php processes the request, finds none of the expected triggers, and hands everything to Joomla as if nothing is there.
A monitoring ping does the same thing. An uptime check that fetches the homepage and looks for a specific string will pass every time, because the string is there and the response is correct.
This is why a site can stay compromised for months without any alarm going off. The front is intact. The back is not.
If a cookie-gated webshell is sitting in your site's root right now, would any outside scanner catch it? A Fastw3b Security Audit is how you answer that in writing: it diffs every file of a backup against the vendor's bytes at your installed version, names every modified core file and every added file that no package ships, with its location and the date it appeared, names the probable entry vector, and hands you a written report with the evidence behind every line and the cleanup quoted on it before anything on your live site changes. The report is yours to take to a host, a developer, or an insurer. Diff your files against the vendor's package →
Five things you can check without any tools
You don't need a security scanner to notice several of the things we found. These five checks take less than an hour on most sites.
1. PHP files in the images or uploads folder. Open your file manager or FTP client and look inside the folder your CMS uses for media uploads. On Joomla that's typically images/. On WordPress it's wp-content/uploads/. Any file ending in .php shouldn't be there. Image uploads are JPG, PNG, GIF, WebP, sometimes PDF. A .php file in that folder is either a mistake or something worse.
2. Core files with a modification date newer than your last update. If you know roughly when you last updated Joomla or WordPress, any core file (index.php, configuration.php, the CMS's own library folders) with a modified timestamp after that date deserves a look. Your server's file manager shows modification dates. An attacker who rewrites index.php can't hide that timestamp unless they also change it, and most don't bother.
3. A plugin or extension folder with a number or suffix on its name. When attackers install a backdoor as a fake plugin or component, the folder name is often close to a legitimate one but not quite: wp-content/plugins/akismet2/, or administrator/components/com_fields_bak/. A quick scan of your plugin and extension directories for anything you don't recognise is worth five minutes.
4. Unfamiliar .htaccess files in unexpected places. A standard Joomla or WordPress install has an .htaccess file in the site root. What it shouldn't have is .htaccess files deep inside template folders, component folders, or the images directory. These are often used to enable PHP execution in folders where the server would otherwise block it, or to redirect specific visitors elsewhere.
5. Your installed CMS version against the vendor's current release. Check your Joomla or WordPress admin panel for the installed version, then go to the vendor's release page and see what's current. Joomla's current releases are listed at joomla.org/announcements and WordPress releases at wordpress.org/news/category/releases. If you're more than one minor version behind, you're running with known public vulnerabilities. The rental site was on a version that had been out of active support for over a year.
A plugin folder with an unusual suffix, or a .php file in your images directory, is not proof of a breach. It's a prompt to look more carefully. You're doing triage, not forensics.
What an outside check can't see
Here's the honest part. Everything in the previous section is useful, but it's also incomplete.
The webshell we found on the rental site answered only to requests that included a specific cookie value. Fetch that URL without the cookie and you get a 404. A network scanner, a malware detection service, an uptime monitor, a search engine's safe-browsing check: all of them send requests without that cookie. All of them would have returned a clean result.
The only way to find it was to open the file and read what was inside it. Not fetch it. Open it, decode the obfuscation, and follow what the code does.
That's what a file-level diff is for. You take a known-clean copy of the CMS at the exact version the site runs, and you compare every file. Anything that doesn't match is a candidate. It's slow work on a site with thousands of files, but it's the only method that's complete.
The manual checks above reduce the surface area. They're worth doing today, for free, because some of what we find in real compromises is exactly as visible as a PHP file in an images folder. But they don't replace a file-by-file comparison, and they don't tell you whether files that look right have been swapped for files that happen to share the same name and size.
What to do today
Start with the five checks above. They cost nothing and they take less time than you'd expect. If any of them turns up something odd, don't move files around or delete anything yet. Note what you found and where.
The free Security Check covers the outside view: what's reachable from a browser, whether known malicious patterns show up in the site's responses, and a comparison of your CMS version against the current release. It's not a file audit, but it's a real starting point and it comes back within 24 hours.
If you want the file-level diff, that's the Security Audit. It's what we ran on the rental site. It's the only method that would have found that cookie-gated webshell.
The thing worth sitting with is the timeline. 8 June to 7 September. The front of that site was fine the whole time. The only reason it came to light was a hosting move that would have happened anyway.
Your site doesn't need to look wrong for something to be wrong.
The service runs on WordPress, Joomla, and PHP frameworks like Laravel or Symfony, where there is a vendor package to diff against. Hand-written custom PHP is reviewed and quoted after a first look.
The free Security Check reads what any visitor can reach and returns a score from 0 to 100 within 24 hours. The $490 Security Audit works from a files-only backup you link to, runs in quarantine, and returns a written report with the evidence for every file that doesn't match the vendor's package, the probable entry vector, and the cleanup quoted before any work starts. Neither the check nor the audit needs access to your live site; only the cleanup does, and only once you approve the quote. The $490 does not credit toward cleanup: the audit is its own product and the report is yours to take to a host, a developer, or an insurer.
Security Monitoring, which runs the outside check on a schedule and alerts on any change, is launching soon and will be offered to audited or cleaned sites only.