Hookery — Faking a Login by Reusing a Public Key
Challenge Overview
Hookery is a webhook service that signs outgoing webhooks with a private key and publishes the matching public key so customers can verify authenticity. The bug: that same key pair was also used to sign login session JWTs on the dashboard. Since the public key is intentionally public, anyone can grab it — and that turns out to be enough to fake a login.
Reconnaissance
The site publishes its public key at a well-known path, on purpose, for webhook verification:
GET /.well-known/webhook-key.pem
After logging in normally and inspecting the session cookie (hk_session), the token decoded to:
Header:
{"typ":"JWT","alg":"RS256","kid":"wh_2026a"}
Payload:
{
"sub": "tester@test.com",
"name": "test2",
"role": "customer",
"company": "tester",
"iat": ...,
"exp": ...
}
The dashboard also exposed a /app/platform link — a more privileged “Platform console” area likely gated by that role field.
Vulnerability — JWT Algorithm Confusion (RS256 → HS256)
Normally, RS256 tokens can only be signed by someone who holds the private key — the public key alone is not enough to forge one. However, JWTs also support HS256, which uses a single shared secret (not a key pair). If the server does not strictly enforce which algorithm a token must use, an attacker can:
- Take the token header and change
algfromRS256toHS256 - Sign the new token using the public key as the HS256 shared secret (the public key is freely downloadable)
- Submit that forged token to the server
If the server’s verification code says “verify this token with the key I have” without also enforcing “and it must be RS256,” it accepts the forged token — because technically, the HS256 signature does check out against the public key material.
Exploitation
Step 1 — Download the public key:
curl https://<challenge-host>/.well-known/webhook-key.pem -o pubkey.pem
Step 2 — Decode the real session token to confirm the algorithm and payload structure.
Step 3 — Forge a new token by hand with:
-
algchanged fromRS256toHS256 -
rolechanged fromcustomertoadmin - Signed using the public key as the HS256 secret
Most modern JWT libraries refuse to use a public key as an HS256 secret — that refusal is exactly the safety check this attack bypasses, which is why the token must be built manually.
Step 4 — Submit the forged token:
curl https://<challenge-host>/app/platform \
-H "Cookie: hk_session=<forged_token>"
role=admin granted access and the flag was in the response.
Automation Script
hookery_forge.py grabs the public key, builds a forged token with role set to several admin-like values, and tries each against /app/platform until it succeeds:
python3 hookery_forge.py https://<challenge-host>/ -v
Root Cause
Two different jobs — webhook verification and login session signing — were sharing one key pair. That is fine for RS256-only situations. The moment login verification allows any algorithm, a key that is supposed to be public becomes a working password for HS256 forgery.
Remediation
- Never reuse the same key for two different purposes, especially when one job relies on the key being public and the other relies on it being secret.
- Pin the expected algorithm in your JWT verification code — explicitly accept only
RS256and reject any token claiming a different algorithm regardless of whether the signature validates.
Flag
WEBVERSE{**************REDACTED**************}
Enjoy Reading This Article?
Here are some more articles you might like to read next: