Skip to content

Unauthenticated notification API endpoints: financial abuse via phone number purchase, service disruption, and SMTP credential exposure

Critical
simlarsen published GHSA-6wc5-rhvj-cx7f Mar 30, 2026

Package

npm oneuptime (npm)

Affected versions

<= 7.0.0

Patched versions

10.0.40

Description

Summary

Multiple notification API endpoints are registered without authentication middleware, while sibling endpoints in the same codebase correctly use ClusterKeyAuthorization.isAuthorizedServiceMiddleware. These endpoints are externally reachable via the Nginx proxy at /notification/. Combined with a projectId leak from the public Status Page API, an unauthenticated attacker can purchase phone numbers on the victim's Twilio account and delete all existing alerting numbers.

Root Cause

App/FeatureSet/Notification/API/PhoneNumber.ts registers 5 routes on the Express router with zero authentication middleware:

router.post("/search", ...);         // line 31
router.post("/list-owned", ...);     // line 155
router.post("/assign-existing", ...); // line 238
router.post("/purchase", ...);        // line 386
router.delete("/release/:id", ...);   // line 529

Compare with SMS.ts and Call.ts in the same directory, which correctly use ClusterKeyAuthorization.isAuthorizedServiceMiddleware on their routes.

The Nginx config at Nginx/default.conf.template:485-506 proxies /notification directly to the app server:

location /notification {
    rewrite ^/notification(.*)$ /api/notification$1 break;
    proxy_pass $backend_app;
}

No authentication at the proxy layer.

ProjectId Leak via Public Status Page

The phone number endpoints require a projectId (UUID) in the request body. This UUID is leaked by the public Status Page overview API.

Common/Server/API/StatusPageAPI.ts line 4435 selects projectId: true when fetching status page data. Line 2211 serializes the full statusPage object (including projectId) into the JSON response sent to unauthenticated status page viewers.

Any public OneUptime status page exposes its projectId in the overview API response.

Attack Chain

  1. Attacker visits a public OneUptime status page
  2. The overview API response contains statusPage.projectId
  3. POST /notification/phone-number/purchase with the leaked projectId buys phone numbers on the victim's Twilio account ($1-5+ per number, repeatable)
  4. POST /notification/phone-number/list-owned with the projectId enumerates all owned phone numbers
  5. DELETE /notification/phone-number/release/<id> releases each number, killing all call-based alerting

All database queries in PhoneNumber.ts use isRoot: true (lines 79, 189, 286, 305, 369, 426, 445, 512, 554, 598), bypassing all tenant access controls.

Additional Unauthenticated Endpoints

SMTP Config Test (App/FeatureSet/Notification/API/SMTPConfig.ts:25): Unauthenticated. Reads any project's SMTP configuration (hostname, port, username, password, OAuth credentials) using isRoot: true (line 39) and sends email through it. Requires guessing the smtpConfigId (UUID).

WhatsApp Test (App/FeatureSet/Notification/API/WhatsApp.ts:444): Unauthenticated. Sends WhatsApp messages from the organization's Business account to arbitrary phone numbers.

Impact

  1. Financial abuse: Unlimited phone number purchases on the victim's Twilio account. Each costs $1-5+.
  2. Service disruption: Delete all owned phone numbers. All call-based incident alerting stops.
  3. SMTP credential exposure: Read SMTP credentials for any project (requires config UUID).
  4. WhatsApp abuse: Send messages from the organization's WhatsApp Business account.

Suggested Fix

Add ClusterKeyAuthorization.isAuthorizedServiceMiddleware to all 5 PhoneNumber routes, the SMTPConfig test route, and the WhatsApp test route, matching the pattern used by SMS.ts and Call.ts.

Remove projectId from the Status Page overview API response select statement, or filter it from the serialized output.

Severity

Critical

CVE ID

CVE-2026-34759

Weaknesses

Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action. Learn more on MITRE.

Credits