Press Enter to search or Esc to close

Why .htaccess is where hackers hide after you delete the malware

Why .htaccess is where hackers hide after you delete the malware

A Joomla rental site carried 126 weaponised .htaccess files after a full malware cleanup, and every signature scanner missed them because scanners check PHP, not Apache configuration.

If you cleaned your site last month and something still doesn't feel right, this is probably why. A Joomla site we cleaned in 2026 came back clean from the malware scanner while 126 .htaccess files sat untouched in the directory tree, each one doing something it had no business doing. The PHP payload was gone. Everything the attacker needed to put it back wasn't.

What .htaccess actually does

An .htaccess file is a per-directory configuration file that Apache reads on every HTTP request. It can change how PHP executes in that folder, rewrite URLs, restrict access, and add custom error pages. Apache stacks them: rules in a parent folder apply first, then each child folder's rules add on top.

That's how Joomla's clean URL rewrites work. The .htaccess in your site root tells Apache to pass all requests through the CMS routing layer. WordPress does the same thing. Both ship with files in the right places, for the right reasons.

There's nothing stopping an attacker from adding one anywhere else in your document root, though. Apache will read it. No audit trail, no flag. Just another config file in a folder you've probably never opened.

What was on the server: 16 by hand, 126 by scan

The rental company's site had been running for about three months with an attacker inside before the cleanup was called. The first step is always a hand diff: pull a clean copy of the CMS version the site runs, compare it to what's on the server file by file.

On the first pass, 16 weaponised .htaccess files turned up, one in each major directory. Most compromises drop one or two. Sixteen means someone was methodical and had the time to be.

After the hand review, an engine scan of the entire document root ran. The total came to 126 .htaccess files that shouldn't have been there.

A clean Joomla installation ships with roughly four to six .htaccess-related files, all in expected locations: the site root, the administrator directory, and a couple of framework paths. None inside /images/, /media/, /cache/, or /tmp/. If you find one there, it was placed deliberately.

Three things a weaponised .htaccess does

Each of the 126 files on this site was doing one of three things.

Re-enabling PHP execution in a folder that should never run code. Apache has a directive called AddHandler. Add AddHandler application/x-httpd-php .php to an .htaccess inside your image folder, and PHP files execute there. The payload sits disguised as a regular upload. A content filter never checks .htaccess, and the folder looks clean in any directory listing.

Whitelisting a single named file. Some of the files were more surgical. Instead of enabling PHP for everything in the folder, they used a <Files> block: <Files backdoor.jpg> followed by SetHandler application/x-httpd-php. That one file, named to look like a photo, runs as PHP. Every other file in the folder behaves normally.

Redirecting specific request paths. A third pattern uses RewriteRule to route certain requests to the attacker's script. A visitor browsing product pages sees nothing unusual. A bot hitting a specific URL path gets routed to a PHP file the scanner already cleaned, because the .htaccess was routing them to a copy the scanner never found.

The webshell survives as long as any one of these files survives. Removing the PHP payload isn't enough if the .htaccess that enables or routes to it is still sitting in that folder.

Why your scanner didn't flag them

Signature scanners work by reading a file's contents and matching against a database of known malware patterns. That database is built from PHP, JavaScript, and sometimes binary payloads: obfuscated function calls, base64 blobs, eval() chains.

An .htaccess file has none of those. It has Apache directives like AddHandler and RewriteRule, which appear in completely legitimate sites. There's no malware signature for "this AddHandler line is malicious," because the same directive appears in the root .htaccess of most Joomla installs.

The scanner opens the file, finds no PHP, and moves on. Every signature scanner tested on this cleanup behaved exactly this way. The configuration layer is invisible to them by design.

This is how a site can return a clean scan and still be fully compromised. The PHP files are gone. The .htaccess persistence layer that re-enables PHP execution is still sitting in seven image subdirectories. The next time the attacker drops a fresh payload, it runs.

The trap: why 'Require all denied' didn't stop the webshell

Early in this cleanup, a hardening .htaccess was placed in the quarantine folder with Require all denied. The idea was to block access to that folder while the work continued. The webshell kept responding.

The reason is how Apache's configuration hierarchy works. An .htaccess is only honoured if the server's main configuration allows it. The AllowOverride directive in Apache's httpd.conf (or the virtual host block) controls which .htaccess directives Apache will accept in each directory. If it's set to None for a directory, Apache ignores the .htaccess entirely, including a Require all denied you put there for defence.

On shared hosting, you don't control httpd.conf. The host sets AllowOverride All for your document root because that's what makes clean URLs work in Joomla and WordPress. So the attacker's .htaccess works perfectly. And in this case, the defensive one on the quarantine folder did nothing.

The folder had to move outside the document root entirely before Apache stopped serving the webshell. An .htaccess can't defend a folder when Apache's own configuration is the mechanism being exploited.

Is your site's .htaccess layer as clean as the PHP files you last scanned? The rental company's cleanup found 126 rogue .htaccess files after the malware scanner had already returned a green result: every signature-scanner-clean PHP payload was gone, and every .htaccess re-enabling PHP execution in the image and cache directories was still sitting there. The free Fastw3b Security Check reads your site from outside within 24 hours and gives you a score from 0 to 100 with every item keeping it from 100, including the configuration-layer findings a signature scanner walks past. Five answers from your own day are all it takes. You'll see where your .htaccess layer stands right now, what a rogue directive in an image or cache folder is costing you in live exposure, and which single thing is worth fixing first. It's free, nothing runs on your servers, and if the read shows a build is worth it, the $490 Security Audit works from a files-only backup in quarantine: every file hashed against the vendor's bytes, every .htaccess diffed against the vendor's copy, the probable entry vector named, and a written report with the evidence for every line, cleanup quoted before any work starts. Read what's in my .htaccess layer, free →

What to check on your site today

You don't need paid tools to start. Here's what to do with SSH access or a file manager.

List every .htaccess file on your site. With SSH: find /path/to/your/site -name ".htaccess" -type f. Compare the count against what your CMS ships. A clean Joomla 4 or 5 install has around four to six. A clean WordPress install has one or two. Anything beyond that needs a read.

Focus on these directories first. /images/, /media/, /cache/, /tmp/, /logs/, and any plugin-generated upload folder. Your CMS has no legitimate reason to put an .htaccess inside an image directory. If one's there, open it.

Look for these patterns inside each file:

  • AddHandler application/x-httpd-php applied to .php, .phtml, or an image extension like .jpg
  • SetHandler application/x-httpd-php inside a <Files> block
  • A RewriteRule pointing at a file path inside an upload or cache directory
  • Options +ExecCGI

Any of these in a folder that shouldn't execute code is a finding, not a false positive.

Diff against the vendor's copy. Download a clean archive of your CMS version from the official source. Compare its .htaccess files to yours. The diff should be empty, or contain only rules you added deliberately.

One caveat before you wipe them all

If your site uses custom rewrite rules, cleaning the .htaccess layer without recording them first will break the site in a different way.

Common examples: a plugin that writes its own rewrite rules on activation, an HTTPS redirect in the root .htaccess, a custom ErrorDocument for 404 pages, a header passthrough for a CDN. These usually live in the root file, but not always.

Before touching anything, export every .htaccess on the server. Read through each file and note every line you recognise as intentional. That list is what goes back in after the clean copy is restored.

If you don't know why a line is there, treat it as suspicious. But suspicious still requires a second look before a delete. Take the export first.


Frequently asked questions

How many .htaccess files should a Joomla site have?

A clean Joomla 4 or 5 installation ships with around four to six .htaccess or web.config entries, all in predictable locations: the site root, the administrator directory, and a couple of framework paths. The rental company's site we cleaned in 2026 had 126 that weren't part of any known-clean install. If your count is significantly higher than six, start investigating.

Can WordPress sites have the same problem?

Yes. WordPress uses .htaccess primarily for permalink rewrites at the root. Attackers apply the same technique: place an .htaccess inside /wp-content/uploads/ with an AddHandler directive, and the uploads folder executes PHP. Any .htaccess file found inside the WordPress uploads directory should be treated as a finding until you can confirm otherwise.

Will switching to Nginx fix this?

Nginx doesn't use .htaccess at all. Per-directory configuration in Nginx lives in the main server config, which a web-facing user can't write to. That removes this specific attack surface. It doesn't remove other persistence mechanisms: cron jobs, database-injected redirects, modified core files. The method changes; the need to audit your files doesn't.

How long can a weaponised .htaccess go undetected?

The rental company's site ran for at least three months with the attacker present before anyone called for a cleanup. The owner noticed unusual server behaviour first, not a scanner alert. Signature scanners running on a daily schedule missed the configuration files throughout that period. Without a file integrity monitor that compares against a verified-clean baseline, there's no automatic way to catch this category of persistence.

What is AllowOverride and can I restrict it?

AllowOverride is set in Apache's main configuration or virtual host block. It controls which .htaccess directives Apache will honour in a given directory. AllowOverride All means Apache accepts everything in your .htaccess files, including an attacker's additions. On a VPS or dedicated server you control, restricting it to AllowOverride FileInfo Options removes some of what an attacker can do through a rogue .htaccess. On shared hosting, you typically can't change it at all.

What the scanner misses is what the $490 Security Audit reads: every file of a backup against the vendor's bytes, every .htaccess against the vendor's copy, in quarantine, in 3 days, on Joomla and WordPress sites and PHP frameworks. The check that starts it is free and back within 24 hours from outside only, nothing on your live site touched. The audit is $490, delivered in 3 days from a files-only backup you link to; the report is yours to take to your host, a developer, or an insurer, and the $490 is not credited against cleanup, because the report stands on its own. Cleanup and hardening is a custom quote after the audit, priced on what the report found before any work starts.

If your signature scanner came back clean but you found more .htaccess files than your CMS ships, that gap is exactly what the outside check surfaces first.

See what the outside check finds on your site →

Related Articles

  • Client Login

    Restore password
  • New Registration

Make sure @fastw3b.com email domain is white-listed in your email client to restore password, verify registration, get order confirmations, etc.