The Exploit HQ

Web Application Security for Beginners · lesson 4 · 2026-09-04 · updated 2026-10-12

Parameter Tampering Explained — Web Application Security for Beginners

Before you start

Welcome back to The Exploit HQ, and to part four of our beginner series on web application security. As always, you need zero experience to follow along.

In parts one through three, we found things the server left out in the open: someone else’s basket, a file it was never meant to share, a forgotten backup holding the database password. Today the attack is different. We are not going to look at something that was left lying around. We are going to send the server a message, and we are going to change that message before it arrives.

The word we need first is parameter.

What is a parameter?

When you click ‘Add to cart’ on a shopping site, your browser does not just say ‘add something’. It sends a message with specific details: which product, how many, and which basket it belongs to. Those pieces of information are the parameters. The server reads them and acts on them. The vulnerability we are exploring today happens when the server acts on them without checking whether the values are sensible.

So parameter tampering means this.

What is parameter tampering?

In normal use, your browser sends these messages automatically. But there is nothing stopping you from sending them yourself, directly from the terminal. And that is exactly what curl lets us do. You already know it.

The tools we will use

curl is our browser from earlier parts. Dash capital X PUT is new today. PUT is the instruction that means update something that already exists — as opposed to POST, which creates something new. Dash d is where we put the data we are sending, the parameters we want the server to act on. And JQ formats the server’s reply so it is easy to read.

Let’s start by doing something completely normal: reading the product catalog, just like a shopper would.

Step 1 — read the product catalog

curl -s http://127.0.0.1:3000/api/Products | jq '.data[0] | {id, name, price}'
Part What it does
curl -s fetch quietly, no progress bar
/api/Products the shop’s product list — no login needed to browse it
| jq '.data[0] | ...' take the very first product and show its id, name, and price

This is the shop’s public product list. We are pulling out the very first item and asking for three things: its ID number, its name, and its price. That price lives on the server, in the database. The browser does not decide it — the server does. Open the terminal and type it with me.

Apple Juice at one dollar ninety-nine, and its product ID is one. The price is set here, server-side. But there are other parameters the browser does send, and one of them is about to cause a problem. Let’s add this juice to our basket.

Here is the full message the browser sends when you click ‘Add to cart’.

Step 2 — add Apple Juice to our basket

curl -s -H "Authorization: Bearer $(cat lab/token.txt)" -H "Content-Type: application/json" http://127.0.0.1:3000/api/BasketItems/ -d '{"ProductId":1,"BasketId":6,"quantity":1}' | jq '{item: .data.id, product: .data.ProductId, quantity: .data.quantity}'
Part What it does
-H "Authorization: Bearer ..." our login token — proves who we are
/api/BasketItems/ the endpoint that adds items to baskets
-d '{ProductId:1, BasketId:6, quantity:1}' the parameters: which product, which basket, how many
| jq '{item, product, quantity}' show the three fields the server confirmed

We are sending three parameters in that dash d data block. Product ID is one, that is the Apple Juice. Basket ID is six, that is our basket. And quantity is one. The server is going to trust all three of those. Let’s send it.

The server confirmed it. It created basket item number nine, holding product one, with a quantity of one. That item ID nine is important. It is the handle we will use to update the item in the next step. Remember it.

Now comes the tamper. Quantity is one. What if we tell the server to change it to negative one?

Step 3 — send a negative quantity

curl -s -X PUT -H "Authorization: Bearer $(cat lab/token.txt)" -H "Content-Type: application/json" http://127.0.0.1:3000/api/BasketItems/9 -d '{"quantity":-1}' | jq '{item: .data.id, quantity: .data.quantity}'
Part What it does
-X PUT UPDATE this record — it already exists, we are changing it
/api/BasketItems/9 target: basket item number 9, the one we just created
-d '{quantity: -1}' the tampered parameter — a NEGATIVE number of items
| jq '{item, quantity}' confirm exactly what the server recorded

We are sending a PUT to basket item nine with a quantity of negative one. No shopping site in the world would let you buy negative one of something through its normal user interface. The question is whether the server checks the number for sanity before storing it.

Let’s find out.

It accepted it. Item nine, quantity negative one. The server recorded a negative quantity of Apple Juice without any complaint. Let’s read the basket to see what it looks like from the shopper’s side.

Read our basket to confirm.

curl -s -H "Authorization: Bearer $(cat lab/token.txt)" http://127.0.0.1:3000/rest/basket/6 | jq '{item: .data.Products[0].name, price: .data.Products[0].price, qty: .data.Products[0].BasketItem.quantity}'

Apple Juice at one ninety-nine, quantity negative one. In a real checkout, the line total is price times quantity. Negative one times one ninety-nine would be negative one ninety-nine. The shop would owe the attacker money instead of the other way around. That is the financial impact of one missing check. Now, does the server check anything at all about quantity? Let’s test the other direction.

Let’s try sending ninety-nine and see what happens.

Testing the upper limit

curl -s -X PUT -H "Authorization: Bearer $(cat lab/token.txt)" -H "Content-Type: application/json" http://127.0.0.1:3000/api/BasketItems/9 -d '{"quantity":99}'

You can order only up to five items of this product. The server blocked ninety-nine. So there is validation code here, and the developer who wrote it thought about the upper limit. They just did not think to ask what happens if the number goes below zero. That gap, one side of the fence missing, is one of the most common forms of parameter tampering in the wild, because developers tend to test the paths they expect, not the paths an attacker might try.

Parameters are not always numbers. Let’s look at a completely different kind: a text field that carries identity.

Parameters can be names too

When you post a review on a shopping site, your browser sends an author field with your name or email. In a properly secured app, the server ignores that field completely and looks up your identity from your login session instead. But if the server trusts whatever you send, you can put any name you like in that field. Including the site administrator’s.

Here is the request that posts a product review.

Step 4 — forge a review as the admin

curl -s -X PUT -H "Authorization: Bearer $(cat lab/token.txt)" -H "Content-Type: application/json" http://127.0.0.1:3000/rest/products/2/reviews -d '{"message":"I personally approve this product.","author":"admin@juice-sh.op"}' | jq '.status'
Part What it does
/rest/products/2/reviews post a review for product number 2
"message": "..." the review text we are submitting
"author": "admin@juice-sh.op" the TAMPERED field — the admin’s address, not ours
| jq '.status' check whether the server accepted or rejected it

We are logged in as our demo account. But in the author field we are sending the administrator’s email address. If the server uses our real session identity, it will reject this or overwrite the author with our real address. If it trusts the field we send, it will store exactly what we typed. Let’s find out which one.

Success. Now let’s check what the review looks like to anyone who visits the product page.

Check the product two reviews.

curl -s http://127.0.0.1:3000/rest/products/2/reviews | jq '[.data[] | {author: .author, message: .message}] | last'

There it is. The admin’s email address, with our message. Anyone visiting this product page sees a review that appears to come from the site administrator. We are logged in as demo at ehq dot local, but the review says admin at juice dash sh dot op. The server stored exactly what we put in the author field, with no comparison against our actual login. That is identity-level parameter tampering, and it has the same root cause as the quantity problem: a parameter that came from the browser, accepted without verification.

How do you find this, and how do you fix it?

Find it, and fix it

To find it, look for any value the app accepts from your browser and acts on. Numbers: try zero, try negatives, try values far outside the expected range. Text fields like author or user ID: try substituting someone else’s identifier. The key habit is testing all the edges, not just the ones the developer intended. To fix it, move all validation to the server and apply it in both directions. The user interface can make it hard to type a negative number, but anyone with curl can send any value they like and bypass the interface entirely. For identity fields specifically, the fix is absolute: never trust a name or identifier the browser sends for who you are. Use the session the server issued when the user logged in.

And one rule, every single time.

Before you go

Only ever test apps you own or have explicit written permission to test. Sending a crafted parameter to a site you do not own is still an unauthorized test, even if it looks harmless. Juice Shop is built for exactly this kind of safe, legal practice, running entirely on your own machine. The O-WASP project lists this class of flaw under A-03 Injection and A-04 Insecure Design in their top ten web application risks. In part five we are going to look at authentication bypass, where we skip the login gate entirely. Thanks for learning with The Exploit HQ, and I will see you there.