Skip to content
OopsSec Store - Walkthroughs
Go back

Client-Side Price Manipulation

Edit page

OopsSec Store’s checkout sends the order total straight from the browser. The server saves whatever it receives without recalculating from actual product prices. Change it to a penny, the order goes through at a penny.

Table of contents

Open Table of contents

Lab setup

From an empty directory:

npx create-oss-store oss-store
cd oss-store
npm start

Or with Docker (no Node.js required):

docker run -p 3000:3000 leogra/oss-oopssec-store

The app runs at http://localhost:3000.

Vulnerability overview

When you buy something on OopsSec Store, the browser sends a POST to /api/orders with the cart items and a total field. That total is calculated by frontend JavaScript. The server takes it at face value and creates the order.

The product prices are in the database. The server could look them up and do the math itself. It doesn’t.

Locating the attack surface

Add some products to your cart and go through checkout. The payment page shows your order summary with the total.

Checkout page displaying order summary and payment button

Click “Complete Payment” and the browser fires off a POST with the order details, including the total the frontend calculated.

Exploitation

Configuring the proxy

Set up Burp Suite as an intercepting proxy (browser traffic through 127.0.0.1:8080). Leave interception off for now.

Preparing the order

Add products to your cart. Higher-priced items make the result more obvious. Go through checkout until you hit the payment page.

Product page showing item to be added to cart

Intercepting the request

Turn on interception in Burp, then click “Complete Payment”. Burp catches the POST to /api/orders before it hits the server.

Burp Suite intercept toggle enabled

Looking at the request

The request body is JSON with the order details:

Intercepted POST request showing order JSON with total field

The total field is the price the frontend calculated. The server uses this number directly.

Modifying the price

Change total to whatever you want. 0.1 works:

Modified request with total changed to 0.1

Completing the attack

Forward the modified request and turn off interception. The server processes the order at your price.

Capturing the flag

The order confirmation shows the purchase at the modified total. The server notices the mismatch and returns the flag:

OSS{cl13nt_s1d3_pr1c3_m4n1pul4t10n}

Order confirmation showing manipulated price and captured flag

Vulnerable code analysis

The checkout handler pulls total straight out of the request body and saves it:

const { total } = await request.json();

const order = await prisma.order.create({
  data: {
    userId: user.id,
    total: total, // Client-provided value used directly
  },
});

The frontend does calculate the right number. But the server never checks it. Anyone with a proxy, devtools, or curl can send whatever total they want.

The product prices and cart quantities are right there in the database. The server just doesn’t use them.

Remediation

Recalculate the total server-side

Pull the cart from the database and compute the total from actual prices:

const cart = await prisma.cart.findFirst({
  where: { userId: user.id },
  include: {
    cartItems: {
      include: { product: true },
    },
  },
});

const calculatedTotal = cart.cartItems.reduce(
  (sum, item) => sum + item.product.price * item.quantity,
  0
);

const order = await prisma.order.create({
  data: {
    userId: user.id,
    total: calculatedTotal, // Server-calculated value
  },
});

Detect tampering

If you still want the client total for logging or display, compare it against the server calculation:

const clientTotal = requestBody.total;
const serverTotal = calculateTotalFromCart(cart);

if (Math.abs(clientTotal - serverTotal) > 0.01) {
  return NextResponse.json(
    { error: "Price validation failed" },
    { status: 400 }
  );
}

The frontend total is fine for UX. The backend should never trust it for the actual charge.


Edit page
Share this post on:

Previous Post
Server-Side Request Forgery: Accessing Internal Pages via Support Form
Next Post
Mass Assignment: Admin Privilege Escalation via Signup