When you drop a sweet into your bag, your browser sends the shop a small form, and the
price is one of the fields inside it. The shop charges whatever price that field says.
Because the field lives in your browser, you can edit it before you click Add to bag,
and the shop will believe the new number. Set it to 1 and a bag of humbugs
costs you a penny. 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.
Make sure the shop is running at http://localhost:3000.
You do not need any special tools for this one, just your browser’s
developer tools (right-click the page and choose Inspect, or press
F12 on Windows / Cmd+Option+I on a Mac). Any
grey-box account works.
1 · Find it
Open a sweet’s page (click a sweet on the counter) so you can see its Add to bag
button. When you click that button, the page quietly sends the price along with it, in a
hidden field the shop trusts without re-checking. You can see it: in dev tools,
open the Elements tab (Chrome/Edge) or Inspector tab (Firefox),
and search (Cmd/Ctrl+F inside that panel) for name="price". You
will find something like:
<input type="hidden" name="price" value="150">
That value="150" is 150 pence, the £1.50 the fudge should cost. The shop takes it at
face value. That is the whole flaw.
2 · Exploit it
Change the price right there, before you add it to the bag. In the Elements/Inspector
panel, double-click the 150 in that hidden field, type 1 instead, and press
Enter. The field now reads:
<input type="hidden" name="price" value="1"> Now click the sweet’s Add to bag button as normal.
The fudge lands 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:
const price =
parseInt(req.body.price, 10);
cart[id] = { qty, unit_price_pence: price }; 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.