WebHuntress CTF 2026
Trust the Build
A Go (Fiber) update-manifest activator. You get a signed manifest:
{"package":"local-telemetry-adapter","version":"1.0.13078","artifact":{"build":"release-13078","hook":"passive_health"},"artifact_sha256":"c25168d2...9766","signature":"62aa5d91...f321"}
The instinct is to forge the signature — don't bother, it's a keyed MAC with a server-side secret (I tested every obvious preimage; none match). The real question is what the signature covers. Mutating fields one at a time shows it:
- change
artifact_sha256/ blank it / dropsignature→signed_manifest_rejected(so the MAC covers the hash) - change
package/version→update_not_found(catalog key) - change
build→artifact_not_supported(artifact selector) - change
artifact.hook→ accepted!
The hook is what the policy maps to routine vs restricted (passive_health = routine, privileged_export = restricted), and it sits outside the signed scope. So you replay the legitimately-signed routine manifest verbatim and just flip the hook:
curl -s https://<host>/inspect-4e00a5 -H 'Content-Type: application/json' \
-d '{"package":"local-telemetry-adapter","version":"1.0.13078","artifact":{"build":"release-13078","hook":"privileged_export"},"artifact_sha256":"c25168d2...9766","signature":"62aa5d91...f321"}'
{"effect":"unverified_update_activated","ok":true,"evidence":"proof_..."}
The integrity contract binds the signature to the artifact bytes but not to the field that actually drives authorization. Lovely bug.