🛡️ CVE-2026-52841 — easyappointments

🟢 CVSS 3.1 — Low ✅ No Known Exploit CWE-639 NVD
3.1
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

Easy!Appointments: Authorization bypass in Google OAuth provider binding lets any backend user rebind a peer provider's Google sync

Summary

Google::oauth at application/controllers/Google.php:278 stores its URL-supplied provider_id in the session, and oauth_callback saves the issued Google OAuth token against that row without checking the caller owns the provider. Any logged-in backend user (admin, provider, or secretary) rebinds a peer provider's Google sync to a Google account they control. The peer's appointments then sync into the attacker's calendar with each customer's name and email attached as attendee data.

Preconditions

  • Attacker holds a backend login on the target instance (admin, provider, or secretary). The customer role cannot log in.
  • The instance has Google Calendar OAuth configured at application/config/google.php - i.e. any deployment that uses the Google sync feature at all.
  • Default deployment per the project's own docker-compose.yml; no non-default flags required.

Details

```php

// application/controllers/Google.php:278-289

public function oauth(string $provider_id): void

{

if (!$this->session->userdata('user_id')) {

show_error('Forbidden', 403);

}

// Store the provider id for use on the callback function.

session(['oauth_provider_id' => $provider_id]); // (*) attacker-chosen id stored unchecked

// Redirect browser to google user content page.

header('Location: ' . $this->google_sync->get_auth_url());

}

```

```php

// application/controllers/Google.php:305-337

public function oauth_callback(): void

{

if (!session('user_id')) {

abort(403, 'Forbidden');

}

$code = request('code');

if (empty($code)) { response('Code authorization failed.'); return; }

$token = $this->google_sync->authenticate($code);

if (empty($token)) { response('Token authorization failed.'); return; }

$oauth_provider_id = session('oauth_provider_id');

if ($oauth_provider_id) {

$this->providers_model->set_setting($oauth_provider_id, 'google_sync', true); // (*)

$this->providers_model->set_setting($oauth_provider_id, 'google_token', json_encode($token)); // (*)

$this->providers_model->set_setting($oauth_provider_id, 'google_calendar', 'primary');

} else {

response('Sync provider id not specified.');

}

}

```

The same controller already carries the right gate on every other sync-management entry. select_google_calendar at application/controllers/Google.php:389 and disable_provider_sync at application/controllers/Google.php:423 both refuse the call when the caller is neither an admin nor the provider themselves:

```php

// application/controllers/Google.php:389

if (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {

throw new RuntimeException('You do not have the required permissions for this task.');

}

```

oauth and oauth_callback skip that check. Once the callback runs with oauth_provider_id pointing at a peer provider, the peer's user_settings row is overwritten with the attacker's OAuth token and google_sync is forcibly enabled.

The attack chain that delivers the data:

  • Synchronization::sync_appointment_saved at application/libraries/Synchronization.php:51 runs on every booking save. The path includes the unauthenticated public booking flow (Booking::register at application/controllers/Booking.php:463) and the backend save (Calendar::save_appointment at application/controllers/Calendar.php:306). When $provider['settings']['google_sync'] is truthy the handler reads google_token from the row - now the attacker's - refreshes it, and calls Google_sync::add_appointment.
  • Google_sync::add_appointment at application/libraries/Google_sync.php:184-189 adds the customer as a Google calendar attendee with their first name, last name, and email.
  • The cron-triggered Console::sync -> Google::sync($provider_id) at application/controllers/Google.php:44 walks the existing sync_past_days and sync_future_days windows and pushes every appointment to the attacker's calendar.
  • The same loop deletes the local row whenever the remote event throws or is cancelled (application/controllers/Google.php:186-191); the attacker rolls events out of their Google calendar to delete the victim provider's appointments. Events the attacker creates in their own calendar arrive as unavailability records on the victim's schedule (application/controllers/Google.php:209-254).

Proof of concept

Setup

1. Clone the repository, pin to the audited release, copy the sample config, and bring up the bundled stack:

```bash

git clone https://github.com/alextselegidis/easyappointments

cd easyappointments

git checkout 1.5.2

cp config-sample.php config.php

docker compose up -d

until curl -fsS http://localhost/ -o /dev/null; do sleep 2; done

```

2. Run the console install

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is high, an attacker needs administrative privileges on the target. A user must be tricked into taking some action. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality low, integrity low, availability none.

Weakness class

CVE-2026-52841 is classified as CWE-639: Authorization Bypass Through User-Controlled Key. An object is selected by an identifier from the request without checking the caller owns it.

Affected software

CVE-2026-52841 is recorded against 2 packages.

  • alextselegidis/easyappointments
  • unknown

Timeline and source

Published on 29 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.

References

github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
github.com (Web)

Details

Severity LOW
CVSS Score 3.1
CVSS Vector CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N
CWE CWE-639
Public Exploit ✅ No
Source NVD
Published 2026-07-29
Updated 2026-08-12
Modified 2026-07-29
Fix URL N/A

Affected Packages

Software From version Fixed in
alextselegidis/easyappointments
unknown

Similar Threats

Free Vulnerability Check

Is your site affected by CVE-2026-52841?

BotEraser helps you identify potentially vulnerable plugins and themes by checking your installation against CVE-2026-52841 and other known CVE records.

Scan My Site Free →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.