Overview

a second-order ddns authz bug in nezha

June 10, 2026
3 min read

TL;DR

I also got CVE-2026-53521 in Nezha.

This one was more interesting than a normal IDOR because it was second-order.

The short version:

  • PATCH /server/{id} accepted nonexistent ddns_profiles IDs
  • those unresolved IDs were stored on the attacker’s server
  • if another user later created a DDNS profile with one of those IDs, the worker would resolve it
  • and the DDNS update path would run using the victim profile configuration in the context of the attacker server

So the cross-user reference was not valid at write time.

It became valid later.

That is what made it fun.

The bug

Nezha already blocked the obvious attack.

If the attacker tried to bind an existing foreign DDNS profile directly, the permission check denied it.

That is the part that makes this bug worth talking about.

The normal boundary existed. It just had a hole in a less obvious place.

The server update path only rejected DDNS profile IDs that already existed and belonged to someone else. If the submitted ID did not exist yet, it was skipped and still persisted into the raw server state.

Later on, the DDNS worker trusted that stored ID, resolved it by ID, and dispatched a DDNS update without revalidating ownership against the server owner.

That means a fake reference could sit there quietly until another user eventually created the real object behind it.

At that point, the stale attacker-controlled reference turned into a live cross-user binding.

Why I liked it

This is exactly the kind of bug I like: the direct attack path is denied, but the delayed version still works.

That usually means the application got the rule right once, then forgot to apply it again in the deferred path.

And that is what happened here:

  • the controller path partially enforced ownership
  • the worker path later trusted stored raw IDs too much

Once you separate those two stages, the issue becomes much clearer.

What the worker actually did

According to the advisory, the validated worker path combined:

  • the victim DDNS profile ID and owner
  • the victim profile provider type
  • victim profile fields such as domains, access ID, access secret, and retry policy
  • attacker server context, including attacker-controlled override domains

That is important because this was not just “the worker can look up the wrong object.”

The worker actually used the victim profile configuration when processing the attacker server.

What I did not claim

The advisory was clear about the limits, and I like that boundary.

What was not confirmed:

  • direct credential disclosure to the attacker
  • guaranteed external DNS modification across every provider
  • account takeover

The credentials stayed server-side in the validated path.

The downstream impact depended on the provider configuration and on what the victim provider account was allowed to update.

So this is not a “full compromise” bug. It is an authorization flaw in a deferred worker path with real downstream effects.

Severity and versions

The public advisory lists:

  • CVE: CVE-2026-53521
  • Severity: Moderate
  • CVSS: 6.4
  • Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:L
  • Affected: >= 2.0.14, < 2.1.0
  • Patched: 2.1.0

The advisory also notes that the issue was confirmed on:

  • Nezha v2.0.14
  • commit 8b5e382fe217107c7b777ea9c6b4bc3d2e156202

The practical catch

This bug was not frictionless.

The attacker had to predict or prebind future DDNS profile IDs, which limits severity.

But that does not make it fake.

The unresolved IDs were stored, survived reload, and became active later if the predicted ID got assigned to another user’s new DDNS profile.

That is enough for a real second-order authz issue.

Reference

Thanks for reading this blog post all the way to the end