intigriti 0726 duplicate key json
Program
Intigriti Challenge 0726
Code
INTIGRITI-W10RNNAA
Asset
https://challenge-0726.intigriti.io (Tier 2)
Component
POST /api/manifests/sign → POST /api/publications
Type
Improper Access Control (Generic)
Severity
Medium
Status
Accepted
Created
27/07/2026
Last updated
01/08/2026
Summary
Any registered user can get a valid signed approval for the restricted core namespace and use it to read @core/security-notes v1.0.0, the protected report holding the flag.
The signing pipeline splits the authorization check from the action:
The two stages handle duplicate JSON keys differently. Give the manifest two top-level "package" keys and the signer takes the first while the publisher takes the last. One unmodified byte string then means "my namespace" to the check and core to the report generator.
The digest itself is solid. I signed an honest manifest, swapped in the core body before publishing, and got back 400 "Publication approval is invalid." Nothing here forges or tampers with a signature. Every check passes for real. The two halves just answer different questions about the same document.
Steps to reproduce
Environment
Attacker: freshly registered account
touhemi-zx91k4, namespace@touhemi-zx91k4-9b9ffe17. No special role, no elevated permission.Target:
@core/security-notesv1.0.0, found via/api/observatory/advisories,/catalogand/references.Tested: 27/07/2026
Step 2 uses hello-world because every new namespace is seeded with compat-sample, hello-world and legacy-adapter.
The approval expires in about 15 minutes, counted down in Manifest Studio. Publish before it lapses, or step 4 fails on expiry instead.
Build the dual-reading manifest
Two "package" keys, own namespace first and core second. Exact bytes, single line, no whitespace between tokens:
The sha256 of the decoded bytes is 8f94c0a774cfaa922f18039b6a0c56b918e6129c189fb2c605739b3d34808d11. That matches the manifest_sha256 returned in the next step and the digest shown in the screenshot, so you can confirm nothing was altered between signing and publishing:
Flag
Payload
The whole exploit is the two "package" keys:
Full reproduction from a fresh account. Register, then paste this into the DevTools console on https://challenge-0726.intigriti.io:
The digest changes with the namespace, since key #1 carries your own namespace string. A different digest on your reproduction does not indicate a failed run.
Control cases confirming the root cause
Manifest sent to /manifests/sign
Result
plain, own scope
201 (harness works)
plain, scope:"core"
400 (scope check works)
duplicate package, own first, core second
201
duplicate package, core first, own second
400
"core" (unicode-escaped core)
400
own manifest signed, then core body swapped in at publish
400, approval invalid
Rows 3 and 4 are the finding. Same structure, only the key order differs, opposite results. A filter that simply missed core would let both orders through, so only a first-key-wins parser explains this. Row 5 shows the check runs on decoded values rather than raw text. Row 6 shows the digest binding is intact.
Impact
Anyone who registers a free account can read a report from the restricted core namespace. The app clearly means to withhold it, since asking for that scope directly returns a 400. In this instance the report holds the flag, INTIGRITI{019f8700-4613-74fb-923e-781903e4bee9}.
The namespace boundary is what keeps tenants apart. Approvals get issued for one scope and honoured as another, so that boundary fails for any private namespace on the instance, not just core. The scope in the second "package" key is entirely attacker-chosen.
What it takes: one free account, no victim interaction, no elevated role, no race or timing window, two requests.
Registry data is untouched, so this is a confidentiality issue only.
Worth noting for detection. Each publication is created under the attacker's own approval and appears in their own history, so nothing about the traffic looks different from normal use.
Recommended solution
Parse the manifest once. Derive both the authorization decision and the report target from that single parsed object, instead of letting two components read the same bytes independently.
Reject manifests with duplicate keys rather than quietly resolving them. Either parse strictly, or canonicalize with RFC 8785 (JCS) and sign the canonical form. RFC 8259 leaves duplicate-key behaviour undefined, so any design that depends on two parsers agreeing about it will break sooner or later.
Defence in depth: re-check scope ownership against the session at /api/publications, so a mis-issued approval on its own is not enough to read another namespace's data.