← Blog

Joomla

Joomla security audit: the eight checks before code

A client hands you SSH keys to a Joomla site they inherited from an agency that vanished in 2022. Before you fix anything, you audit. Here is the list we use.

Jacob Molkenboer· Founder · A Brand New Company· 3 Jun 2026· 7 min
Closed leather logbook, brass key on cream card, rubber stamp, ink pad, green wax seal, red string tag on ivory desk.

A client emails you on a Tuesday. They've just bought a Dutch wholesaler whose website runs Joomla. The original agency went quiet in 2022. The handover is one Bitwarden share with SSH, FTP, and an /administrator/ URL. They want a feature added by Friday.

Don't touch the code yet. Audit first.

We run the same eight-point check on every inherited Joomla site that lands on our desks. It takes about three hours and it has caught everything from a four-year-old Super User account that nobody remembered, to a quiet image-gallery extension sitting on a known RCE. The order matters. So does what you do with each finding.

A note on timing: automated CMS scanners crawl the public IPv4 space on a roughly weekly cadence. A forgotten Joomla install behind a forgotten subdomain gets fingerprinted within hours of the next sweep, and any extension that matches a row in the Vulnerable Extensions List is queued for an exploitation attempt. The audit window is not optional.

1. Core version and the end-of-life clock

Open the admin footer or read libraries/src/Version.php directly. Map that against the official Joomla support window. Joomla 3.x went end-of-life on 17 August 2023. Joomla 4 receives security fixes through to its own retirement. Joomla 5 is the current line. The published roadmap is at docs.joomla.org/Joomla!_CMS_versions.

If the site is on 3.x, you are not auditing a website, you are auditing a liability. Document the version, the EOL date, and the migration cost in the same paragraph of your handover note. The client needs to see those three numbers next to each other.

grep -E "RELEASE|MAINTENANCE" libraries/src/Version.php
# RELEASE = '4.4'
# MAINTENANCE = '8'

2. The extensions inventory against the VEL

The Joomla Vulnerable Extensions List at vel.joomla.org is the single most important resource you will use this hour. Every third-party extension installed on the site goes into a spreadsheet with: name, version, vendor, install date, last update.

Then you cross-reference each row against VEL. Any match is a finding. Any extension whose vendor's website returns a 404 is a finding. Any extension last updated before 2022 is a finding even if VEL is clean, because the absence of a recent update on a CMS extension usually means the developer walked away.

Pull the list from the database, not the admin UI. Admin pages can lie when something is half-broken.

SELECT name, element, type, manifest_cache
FROM jos_extensions
WHERE type IN ('component','module','plugin')
  AND protected = 0
ORDER BY name;

Replace jos_ with the actual table prefix from configuration.php.

3. configuration.php exposure

The configuration file holds the database username, password, and host in plaintext. It also holds the secret key used for session tokens. There are two questions you need to answer in under five minutes.

First: is the file readable from the web? Hit https://site.tld/configuration.php directly. It should return blank (PHP parses but the file has no output). Then check whether a backup copy exists at configuration.php.bak, configuration.php~, configuration.old, or inside /tmp/. Older Joomla migrations leave these behind constantly. They are readable.

Second: what permissions are on the file? It should be 444, owned by the web user. If you find 644 on a shared host, the credentials are already exposed to every other tenant on that machine.

find . -maxdepth 2 -iname "configuration*" -ls

4. Super User accounts and the admin login surface

Open /administrator/index.php and check the user list. You are looking for three things.

One: how many Super User accounts exist, and which of them belong to people the current client knows. Disable the rest. Do not delete yet, you need them for the audit trail.

Two: is the /administrator/ path protected by an extra layer (htpasswd, IP allowlist, Cloudflare Access)? If not, the path is publicly enumerable and rate-limited only by Joomla's own brute-force logic. We add an htpasswd in front of it on every site we inherit, before doing anything else.

Three: is two-factor enabled on every remaining Super User? Joomla has had built-in 2FA since 3.2. If it's off, turn it on for the remaining accounts before you leave the audit session.

Warning

If you find a Super User account named after a person who left the original agency, do not delete it during the audit. Disable it, log the email address, and ask the client whether that address is still under their control. Several of the inherited sites we've worked on had abandoned addresses that someone else had since claimed.

5. File permissions and ownership

The classic Joomla deployment story: someone ran chmod -R 777 to fix a stuck install, then never reverted it. You will find this on roughly one in three inherited sites.

The correct posture on a Linux host: directories 755, files 644, configuration.php 444, owned by the web user (often www-data or a per-site user on cPanel-style hosts). Anything writable that doesn't need to be writable is a finding. Anything owned by root inside the webroot is a finding.

find . -type f -perm /o+w -ls    # world-writable files
find . -type d -perm /o+w -ls    # world-writable directories
find . -user root -ls            # files owned by root inside webroot

The tmp/ and cache/ directories should be writable, but their parent should not be. Old Akeeba backup archives sitting inside administrator/components/com_akeeba/backup/ are a finding regardless of permissions, because they often contain a full database dump.

6. Database user privileges

Read configuration.php for the MySQL credentials, then log into the database and check the grants on that user.

SHOW GRANTS FOR 'site_user'@'localhost';

The Joomla user needs SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, INDEX on its own database. It does not need GRANT OPTION, it does not need SUPER, and it does not need access to other databases. If you find ALL PRIVILEGES ON *.*, that goes in the first paragraph of the report.

While you're connected, check the jos_users table for accounts you didn't see in the admin UI, and the jos_session table for active sessions older than a day. Either can hint at backdoor access.

7. Cron jobs, scheduled tasks, and the forgotten one-liner

This is the check that has caught the most live compromises for us. Look in three places.

Server crontab for the web user (crontab -l -u www-data), system cron (/etc/cron.d/, /etc/cron.daily/), and Joomla's own Scheduled Tasks under System → Manage → Scheduled Tasks in Joomla 4 and 5.

What you're looking for: any cron entry that fetches a URL with curl or wget, any entry that runs a PHP file outside the Joomla webroot, any entry that touches /tmp/. We've found cron jobs that pulled a fresh copy of a webshell every six hours from a domain the original agency had let lapse, which a new owner had since registered.

For published advisories on Joomla core itself, the Joomla Security Centre is the authoritative source. Read it once before the audit so you know which CVE patterns you're looking for.

8. Backup and rollback posture

Last and least exciting. Before you change a single byte, you need to be sure you can roll back to the state the site is in right now.

The question to answer: if I run a destructive command in five minutes, can the client be live again within two hours? If the answer is no, you build a backup first. Akeeba Backup is the de facto tool for Joomla, and the JPA format restores cleanly via Kickstart. We install Akeeba on every inherited site as step zero, configure it to write to off-site storage (S3, Backblaze, or sftp to a separate host), and run a test restore to a staging URL before touching anything.

Takeaway

A backup you have never restored is not a backup. It is a folder.

Closing the audit

Three hours, eight checks, one report. The deliverable is a single document with three columns: finding, severity, fix cost in hours. The client signs off on which findings get fixed before the feature work starts, and which get filed as next quarter.

About a third of the time the audit ends the engagement. The site is on Joomla 3, the extensions are abandoned, the credentials are already in the wild, and the honest recommendation is a rebuild rather than a patch. When we inherited the wholesaler site in this post's opening, that's where it ended too. We rebuilt it on a modern stack as part of a legacy migration, and the new site shipped with the same eight-point audit baked into the handover document for whoever inherits it next.

The smallest thing you can do today: open /administrator/index.php on the oldest Joomla site in your portfolio, count the Super User accounts, and ask yourself how many of those names you still recognise.

Key takeaway

A Joomla site you inherit is a liability until you have done the audit. Three hours, eight checks, one report with finding, severity, and fix cost.

FAQ

How long does the eight-point audit take?

About three hours on a small-to-medium Joomla site. Larger installs with dozens of third-party extensions push it closer to a full day, mostly because the VEL cross-reference and the extensions inventory take longer.

Do you still patch Joomla 3 sites in place?

Rarely. Joomla 3 went end-of-life in August 2023, so a Joomla 3 audit almost always closes with a migration recommendation rather than a patch list. Patching a dead branch buys weeks, not years.

What is the single most common finding?

Stale third-party extensions whose vendor websites no longer resolve. Roughly two thirds of the inherited sites we audit have at least one extension last updated before 2022, regardless of whether VEL lists it.

Can I skip the rest if I hide the admin URL?

No. Hiding /administrator/ helps against opportunistic bots, but it does nothing against an authenticated extension exploit, a leaked database user, or a forgotten cron job. The eight checks exist because the threats live in different layers.

joomlasecuritylegacy sitesmigrationphpmysql

Building something?

Start a project