FreeFungi

Challenges FF-004

Sherbet CWE-602 A04:2021

Half Price Humbugs

Price tampering in the cart POST.

When you drop a sweet into your bag, your browser sends the shop a little form. Tucked inside it is the price. The shop takes that number at face value, so if you change it on the way past, you decide what a bag of humbugs costs. This is price tampering, a business-logic flaw where the server trusts a value it should have worked out itself.

New to this? What is price tampering (business logic)?

A business-logic flaw is a bug in the rules of how the app is meant to work, not in a single line of dodgy code. Nothing is technically malformed here; the shop just trusts the wrong side of the conversation.

Price tampering is the classic example: the price of an item is sent from the browser and the server believes it. Because it is valid HTTP doing exactly what it says, scanners tend to miss it. You have to understand what the value means.

You will need to see and change requests. Use a proxy like Burp or ZAP, or your browser’s dev tools. Any customer login works, including the grey-box accounts.

1 · Find it

Add a sweet to your bag with a proxy running, and look at the request to /cart/add. Alongside the sweet id and quantity is a price:

POST /cart/add
Content-Type: application/x-www-form-urlencoded

sweet_id=12&qty=1&price=150

That price=150 (150 pence, £1.50 of fudge) came from a hidden field on the page. The shop never re-checks it. That is the whole flaw.

2 · Exploit it

Intercept the request and change the price to whatever you fancy. A penny, say:

sweet_id=12&qty=1&price=1

Let it through, and the fudge sits in your bag at 1p. Check out and the order is placed and charged at your price. You just bought £1.50 of fudge for a penny, and the shop thinks that is a perfectly normal sale. Set price=0 and it is free.

Why it matters: this is the bug that quietly costs real shops real money. It rarely throws an error, so it hides in plain sight until the books do not add up.

3 · Understand it

The add-to-bag handler reads the price straight out of the request and stores it, then checkout totals from what it stored:

app.post('/cart/add', (req, res) => {
  const price = parseInt(req.body.price, 10);   // <- from the browser
  cart[sweetId] = { qty, unit_price_pence: price };
  // ...checkout later trusts unit_price_pence
});

Only the browser is ever asked what a sweet costs. The server holds the real prices in its own database, but never looks.

4 · Fix it

Never take a price from the client. Look it up on the server from the id, which is the one thing the client is allowed to choose:

✗ Vulnerable
const price =
  parseInt(req.body.price, 10);
cart[id] = { qty, unit_price_pence: price };
✓ Patched
const sweet = getSweet(id);          // trusted
cart[id] = {
  qty,
  unit_price_pence: sweet.price_pence
};

Now the price the shop charges is the price the shop set. Anything the browser sends about money is simply ignored.

The general rule: the client may choose what and how many, never how much. Totals, prices, discounts and permissions are the server’s to decide. Treat every number that affects money as something to recompute, not receive.