CVE-2026-23622

Easy!Appointments, Easy!Pwnage: CSRF Bypass via Method Confusion

Alon Barad
Alon Barad
Software Engineer

Jan 16, 2026·5 min read

Executive Summary (TL;DR)

The Easy!Appointments scheduler fails to validate CSRF tokens for GET requests. Because the application's controllers inadvertently process data from the URL query string, attackers can perform administrative actions (like creating a new admin user) by sending a victim a crafted link. Fixed in version 1.5.3.

A critical Cross-Site Request Forgery (CSRF) vulnerability in Easy!Appointments allows unauthenticated attackers to create administrative accounts by simply tricking an existing admin into clicking a link. The flaw stems from a logic error where CSRF tokens are only validated on POST requests, while sensitive controllers willingly accept GET parameters.

The Hook: Scheduling Your Own Admin Access

Easy!Appointments is a popular self-hosted scheduler used by businesses to handle everything from haircuts to dental checkups. It’s designed to be simple, efficient, and user-friendly. Unfortunately, in versions 1.5.2 and below, it was also "easy" for attackers to grant themselves full administrative access without ever logging in.

The vulnerability lies in a classic disconnect between the security layer and the application logic. The security team (or in this case, the csrf_verify middleware) assumed that dangerous state-changing operations only happen via POST requests. Meanwhile, the controllers handling those operations were happy to take input from anywhere, including the URL bar. This mismatch created a gaping hole: if you ask nicely via a GET request, the bouncer steps aside, and the application processes your command without checking for an invite (CSRF token).

The Flaw: The "GET is Safe" Fallacy

The root cause of CVE-2026-23622 is a logic error in application/core/EA_Security.php. The developers implemented a global CSRF check, which is generally a good practice. However, they made a fatal assumption: they assumed that if a request isn't a POST, it doesn't need to be checked.

In the HTTP standard, GET requests are supposed to be "idempotent" and "safe"—meaning they should only retrieve data, not change it. The security middleware relied on this convention rather than enforcing it. The code explicitly checks $_SERVER['REQUEST_METHOD']. If it sees anything other than POST (like GET, HEAD, or OPTIONS), it essentially says, "Nothing to see here, go right ahead," and returns early, bypassing the token verification entirely.

This would have been fine if the application actually respected that rule. But PHP's $_REQUEST superglobal (or loose framework input handling) often merges $_GET and $_POST data. This means that when the Admins::store controller asks for a first_name, it doesn't care if that data came from a submitted form or a URL query string. The security layer let the request through because it looked safe (GET), and the controller executed it because it had the data it needed.

The Code: The Sleeping Watchman

Let's look at the smoking gun in EA_Security.php. This is the function responsible for stopping Cross-Site Request Forgery. It decides whether to inspect a request or let it pass.

// application/core/EA_Security.php
 
public function csrf_verify() {
    // VULNERABLE LOGIC
    // The developer assumes only POST requests can change state.
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        return; // The gate opens wide.
    }
 
    // The actual token validation logic is down here.
    // If the attacker uses GET, this code is dead code.
    if ( ! isset($_POST[$this->token_name]) || ... ) {
        // Error handling
    }
}

Now consider the target controller, for example, Admins.php. It likely grabs input using a wrapper that doesn't discriminate by method:

// application/controllers/Admins.php (Conceptual)
 
public function store() {
    // The framework happily pulls 'first_name' from the URL query string
    $admin_data = array(
        'first_name' => $this->input->get_post('first_name'), 
        'email'      => $this->input->get_post('email'),
        // ...
    );
    
    // And saves it to the database, creating a new admin.
    $this->admins_model->add($admin_data);
}

This combination is catastrophic. The security layer ignores the request because of its method, and the logic layer processes the payload regardless of the method.

The Exploit: Clicking Your Way to Compromise

Because the vulnerability is a CSRF bypass, the exploit requires user interaction. The attacker needs to trick a logged-in administrator into clicking a link or visiting a malicious page. This is trivial to achieve via a phishing email (

The Impact: Game Over

This is a High Severity vulnerability (CVSS 7.4) for a reason. In a self-hosted environment, the scheduler is often the gateway to customer PII (Personally Identifiable Information), appointment histories, and internal business logic.

By exploiting this, an attacker can:

  1. Create a new Administrator account: This provides persistent, full access to the application.
  2. Modify existing accounts: Change the password of the current admin (Account Takeover).
  3. Exfiltrate Data: Once logged in as an admin, the attacker can export the entire customer database.
  4. RCE Potential: In many PHP applications, administrative access leads to Remote Code Execution via file upload settings or template editing. If Easy!Appointments allows admins to upload avatars or modify email templates without strict sanitization, the server is compromised completely.

The Fix: Trust No Method

The fix was applied in version 1.5.3. The patch likely involves one of two strategies, or both combined:

  1. Strict Method Checking in Controllers: Ensuring that actions like store, save, and update explicitly reject GET requests.
  2. Universal CSRF Checks: Modifying csrf_verify to run validation on all state-changing endpoints, regardless of the HTTP verb, or simply ensuring the framework forces POST for sensitive routes.

To patch this manually if you cannot upgrade (which you really should):

// Quick and Dirty Mitigation
public function csrf_verify() {
    // Don't just return early. 
    // If it's a sensitive path, ENFORCE POST or CHECK TOKEN.
    if ($_SERVER['REQUEST_METHOD'] === 'GET' && $this->is_sensitive_path()) {
        die('CSRF Attempt blocked: GET method not allowed for this action.');
    }
    
    if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
        return;
    }
    // ...
}

Recommendation: Update to version 1.5.3 immediately. This version correctly handles request methods and token validation, closing the window attacker's were climbing through.

Fix Analysis (1)

Technical Appendix

CVSS Score
7.4/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Affected Systems

Easy!Appointments Self-Hosted Scheduler

Affected Versions Detail

Product
Affected Versions
Fixed Version
Easy!Appointments
alextselegidis
<= 1.5.21.5.3
AttributeDetail
CWE IDCWE-352 (CSRF)
CVSS v4.07.4 (High)
Attack VectorNetwork
User InteractionRequired (Passive)
ImpactAdmin Account Creation / Takeover
Patch StatusFixed in 1.5.3
CWE-352
Cross-Site Request Forgery (CSRF)

The application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.

Vulnerability Timeline

Vulnerability Disclosed & Patch Released
2026-02-15

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.