Doomla! Zero Days
Discovery and Exploitation of two Zero Days from the perspective of a first year Penetration Tester.
Two Zero-Days. One Joomla! Extension. Over 260,000 Sites at Risk.
A critical security advisory has been issued for a popular Joomla! extension used on more than 260,000 websites, exposing them to two newly discovered zero-day vulnerabilities:
CVE-2025-6001 – Cross-Site Request Forgery (CSRF):
Allows attackers to trick authenticated users into performing unauthorized actions without their knowledge.CVE-2025-6002 – Unrestricted File Upload:
Permits arbitrary file uploads, potentially leading to remote code execution or full server compromise.
This article breaks down how I uncovered my first two zero-day vulnerabilities — and gives a look inside a live engagement through the eyes of a first-year penetration tester, where theory met reality in the best (and worst) ways.
Day 1
Visiting the company's main website, I check my Firefox Wappalyzer add-on (as all hackers do) and see the site is running Joomla! as its CMS. Following my methodology, I attempt to enumerate the version to try and get an easy win using a publicly available exploit.
Searching around for ways to enumerate versions led me to joomscan, which is comparable to wp-scan for WordPress, only outdated and hasn't been maintained for over six years. Nevertheless, it was able to identify the version and a few of its plugins — one of them being VirtueMart, (foreshadow?).
The site was running on Joomla! 3.9.23, which is TWO major versions behind the latest Joomla! version. Why the hell would they do that? Looking further into it, almost 66% of all sites using Joomla! as their CMS are running on version 3! I won't go too into the details, but it seems to boil down to the migration from 3.X.X to 4.X.X/5.X.X being a pain in the ass, since it's not backwards compatible and custom templates and extensions won't carry over.
Deceptive Versioning
After attempting (and failing) several public exploits targeting Joomla! core and its plugins, it became clear this wasn’t going to be the quick win I had hoped for. Although the site was still running version 3, the company had been selectively backporting its own security patches instead of applying the full updates. At that point, the version number was just that — a number — no longer a reliable indicator of its actual security posture. No wonder the public exploits didn’t work.
Day 2
Let me preface this by saying very few of our penetration tests are black box, and for good reason. When the customer is paying a pretty penny for us to break their company, they don't want us blocked by the front door. Almost all of them are dark gray-box — let's call it — where if we want to test something further upstream in their environment, they'll provide us access (most of the time).
After providing privileged credentials to their Joomla! site and logging in, I was greeted by VirtueMart, a popular eCommerce plugin that turns your Joomla! site into a digital shopping center. First thing that caught my eye was the version; it was running VirtueMart 3.8.6, a full major release behind the current version. After attempting further public exploits that required privileged access — with no success — I was again at a standstill.
There was a general consensus between the operators that this just might not be exploitable in the three days we had. This was extremely unfortunate at the time because one of the other operators testing their network was able to exploit an internal service and pivot through their entire internal environment. While still a great finding, we still had no initial access vector to preface the attack chain.
We have to pop Joomla!
Immediate thoughts were RCE, SQL injection, XSS — the big hitters of the OWASP Top 10. This led me to start looking around the site as a standard user, trying to find a way to escalate privileges to be able to get to the server itself via injection, or escalate privileges. The issue with the latter being we would still need a way to pivot from an elevated Joomla! user to the system, adding another step to the to-do list.
Day 3
Coming up dry as a standard user, I decided to move my efforts to the Administrator side to try and find a pivot into the system. I noticed that the VirtueMart plugin adds its own file upload functionality to manage it’s various shop features, separate from Joomla’s media manager (and security?).
I attempted to upload a PHP file as a product image, and I was extremely surprised to see that it had worked (what in the CTF is this?)! I quickly browsed to the file path but was greeted with a 403 Forbidden — F%$#! After attempting to upload a second file with a .jpg extension, I browsed to that endpoint and was able to view the image successfully. If VirtueMart lets me upload a .php extension, what the hell is stopping me from viewing it?
Defeating the Sysadmin
On a default, fresh installation, only Super Admins have access to VirtueMart’s media upload functionality. However, this feature allows the upload of any file type, including potentially executable ones, which are accessible externally without authentication. As a result, Super Admins with access to this upload functionality are effectively guaranteed remote code execution (RCE) on the server.
Bad. Ass.
Given that, why can't I get remote code execution then? Turns out Joomla! uses a LAMP stack (Linux-Apache-MySQL-PHP), Apache being the key factor here. The Sysadmin of the site could have added their own security so any files with potentially malicious extensions (e.g., .php) in the available upload directories weren't reachable, but would still exist on the server. After trying some file naming tricks such as .php5, .jpg.php, .PhP — our shell was dead in the water at this point and was not a viable attack path.
CTF Meets Reality
In 2022, HackTheBox released UpDown, a Linux machine with SSH and an Apache server running. The Apache server is exploited by uploading a PHP web shell to the server, but not without a catch.
The attacker is able to upload any file extension, but can be blocked by the server from accessing it if the extension is potentially malicious (sound familiar?). The security measure here is Apache's .htaccess file, which grants Sysadmins the ability to set up rules on a per-directory basis (e.g., no .php extensions).
With that knowledge, it's a safe bet to assume that the Sysadmin of the site I’m hacking on has configured a .htaccess rule that restricts users from viewing files in the upload directories with the .php extension.
Time to Cook.
We know that we can upload any extension, but we might not be able to reach it. We can add a .jpg extension to our web shell, but we get a corrupted image error in response — still unusable. Looking into the .htaccess file behavior, it turns out that any .htaccess file in the current directory will override the rules of any further upstream. This means if we upload our own .htaccess file to the upload directory, it would supersede the site's root .htaccess file, which is currently stopping us.
Popping Joomla!
I crafted my own .htaccess file which would tell the server to treat files with a .jpg extension as if they had a .php extension. I uploaded the .htaccess file to the server and received a 200 success status code (technically a 302 in Joomla!). After browsing to the file path, I was still greeted with a 403 Forbidden — this means the site must be using a whitelist filter saying, "you can only view .jpg extensions in this directory" (looking back, I could have just overwritten this rule in my initial .htaccess file). I then modified my PHP file to have a .jpg extension instead of .php and uploaded it…
Pop.
The .htaccess file technique was successful, granting us remote code execution on the server. Regarding the attack path: while the exploit did require elevated privileges to perform the initial file uploads, once uploaded, the web shell was world-readable and accessible to unauthenticated users.
The question now stands: how do we get from an external, unauthenticated user to here? How can we perform this from the outside with out the necessary permissions? Well, we couldn’t.
What if they did it for us?
A Cross-Site Request Forgery attack is exactly that — coercing victims into performing actions on our behalf, unknowingly. Joomla! has very strong CSRF protection in place to prevent attacks like this. Joomla! uses a random MD5 hash string included in each form submission and unique to each form submission; meaning you would never be able to submit forms on another user's behalf without them performing the initial form request themselves. In other words, you would need the CSRF string beforehand when creating the CSRF exploit in order for the malicious request to be valid. There are multiple file upload functions within VirtueMart, all of which use this CSRF protection… except one.
The VirtueMart media file upload function — which is the primary media manager on VirtueMart — does not contain a CSRF token check. This means that the media manager upload function is vulnerable to a CSRF attack, where we can now craft a malicious link that when clicked by a privileged user on the target system will quietly upload our .htaccess file and web shell. And may I remind you, this web shell has a .jpg extension, into an upload directory with thousands of other images ending with .jpg, good luck finding that one.
Attack path fulfilled.
With no CSRF check in place, we are now able to chain both zero-day vulnerabilities into a one-click, unauthenticated arbitrary file upload via CSRF. No authentication, no user interaction beyond a single click. Nice.
It gets easier?
As it turns out, the .htaccess bypass was implemented by the company’s Sysadmin, not by default. The default behavior allows privileged users to upload any file type, and browse to and execute it. This means the malicious CSRF request now only needs to upload a .php web shell; no .htaccess file necessary. This will get caught much faster since it’s now a .php file in a directory of .jpg’s, hence why adding the .htaccess upload request to the CSRF and using a .jpg web shell would prove to be more persistent over-time.
Timeline
Discovered Vulnerability: 04-04-2025
Initial Disclosure to Vendor: 04-16-2025
Response from Vendor: 04-29-2025
Vendor Released Patch: 05-09-2025
Public Disclosure Date: 06-11-2025
Great post and easy to read and follow thanks.