medium

CVE-2026-53521

Go · github.com/nezhahq/nezha

Summary

Nezha Monitoring: Stored future DDNS profile ID allows unauthorized use of another user's DDNS profile context

Severity
medium
CVSS
6.4
EPSS
0.2% (p13)
CWE
CWE-863
Also known as
GHSA-39g2-8x68-pmx8
Published
2026-06-26
Updated
2026-06-26

Advisory details

Summary

PATCH /server/{id} accepts and persists nonexistent ddns_profiles IDs for a member-owned server. If another user later creates a DDNS profile with one of those IDs, the DDNS worker resolves the stored ID and dispatches an update using the other user's DDNS profile configuration in the context of the attacker's server.

This is a second-order authorization bypass: direct binding to an existing foreign DDNS profile is correctly denied, but an unresolved future ID can be stored first and later becomes a live cross-user reference.

Affected versions

Confirmed on:

The exact affected version range was not determined.

Impact

A normal member who owns a server can prebind one or more future DDNS profile IDs to that server. If another user later creates a DDNS profile with a matching ID, the dashboard DDNS worker can use the victim's DDNS profile/provider configuration for the attacker's server.

In the validated worker path, the dispatched DDNS update combines:

This can result in unauthorized DDNS update attempts using another user's DDNS profile context. The attacker does not need permission to bind the victim profile after it exists.

The following were not validated: credential disclosure, account takeover, or guaranteed external DNS modification across all providers. The credentials remain server-side in the worker path. The downstream DNS impact depends on the victim profile's provider configuration and what that provider account is authorized to update.

Affected components

Root cause

The server update path validates submitted DDNS profile IDs through CheckPermission, but that check only rejects existing objects owned by another user. Nonexistent IDs are skipped.

The updateServer path then persists the submitted raw IDs into DDNSProfilesRaw, along with override domain data. Later, the DDNS worker resolves the stored profile IDs by ID and dispatches provider updates without revalidating that the resolved profiles belong to the server owner.

As a result, an invalid unresolved reference can become a valid cross-user reference after another user creates a DDNS profile with the same global auto-increment ID.

Reproduction summary

The behavior was validated locally with focused regression tests.

Controller chain proof

Test file:

cmd/dashboard/controller/ddns_second_order_test.go

Test name:

TestUpdateServerAllowsFutureDDNSProfileBindingThenResolvesVictimProfile

Command:

go test ./cmd/dashboard/controller -run TestUpdateServerAllowsFutureDDNSProfileBindingThenResolvesVictimProfile -count=1

Result:

pass

The test demonstrates:

  1. A normal member owns server 1.
  2. DDNS profile ID 1 does not exist.
  3. The member updates their server with enable_ddns=true and ddns_profiles=[1].
  4. The request succeeds.
  5. The server persists DDNSProfiles=[1].
  6. Another user later creates a DDNS profile and receives ID 1.
  7. A fresh attempt by the attacker to bind profile 1 is correctly denied.
  8. The previously stored reference remains active and resolves in the DDNS worker path.

Provider-level worker proof

Test file:

service/singleton/ddns_worker_authz_test.go

Test name:

TestUpdateDDNSDispatchesVictimProfileForAttackerServer

Command:

go test ./service/singleton -run TestUpdateDDNSDispatchesVictimProfileForAttackerServer -count=1

Result:

pass

The test proves that the DDNS worker does not merely resolve the victim profile. It dispatches a DDNS update using the victim profile configuration and attacker server context.

Validated assertions include:

Practicality

The attack requires predicting or prebinding future DDNS profile IDs. This limits severity, but does not remove the authorization issue.

Evidence supporting practicality:

This makes the issue semi-practical: exploitation depends on future ID prediction or range prebinding, but the unresolved IDs persist and can become active later.

Expected behavior

PATCH /server/{id} should reject any submitted DDNS profile ID that does not both:

  1. exist, and
  2. belong to the caller or the owner of the server being updated.

The DDNS worker should also avoid trusting stored profile IDs without revalidating ownership before provider resolution or dispatch.

Actual behavior

PATCH /server/{id} accepts nonexistent DDNS profile IDs and persists them. If another user later creates a DDNS profile with a matching ID, the stored reference resolves to that user's profile and is consumed by the DDNS worker for the attacker's server.

Suggested remediation

Apply both bind-time and worker-time validation.

At bind time:

At worker time:

Suggested regression tests:

Security relevance

A direct bind to an existing foreign DDNS profile is already denied, which shows the intended ownership boundary. The issue is that the same boundary can be bypassed by storing a future unresolved ID before the victim profile exists.

The worker later treats the stored ID as trusted and dispatches a DDNS update using the victim profile's provider configuration with attacker server context. This is an authorization issue in a deferred worker path, not merely malformed input.

Limitations

References

Related advisories

Is your project exposed to this? Stateward checks every dependency on every pull request and flags it only if your code actually reaches it.

Check my repo

Summarize with AI

ChatGPTClaudePerplexity

Sources: CISA KEV (public domain), OSV.dev & GitHub Advisory Database (CC-BY-4.0), FIRST EPSS, NVD/CWE (public domain). Served live from the Stateward advisory database.