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
- OWASP Juice Shop (a deliberately vulnerable practice app) from the
bkimminich/juice-shopDocker image, bound to 127.0.0.1:3000. - A demo user registered in Juice Shop, with its login token saved to
lab/token.txtand your own basket id (the lesson uses basket6), and the basket-item id the add step returns (the lesson’s is9); use your own. curlandjq(both preinstalled on Kali).- Only test systems you own or are authorized to test.
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?
- A piece of information your browser sends to the server with a request
- Like filling in a form — ProductId, quantity, author, rating
- The browser sends it. The server reads it and acts on it.
- The question is: does the server CHECK whether it makes sense?
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?
- You send the request yourself, instead of letting the browser do it
- You change one of the values — quantity, price, ID, author name
- The server reads your changed value as if it came from the normal app
- If there is no check, the server just does what you asked
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 — sends a web request from the terminal (a browser with no window)
- -X PUT — a NEW flag: UPDATE an existing record instead of creating one
- -d — the data we send: the parameters, written in JSON
- jq — formats the server’s reply so it is easy to read on screen
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
- The app enforces a maximum of 5 items per product
- So there IS a validation rule — just not in both directions
- The maximum is checked. The minimum is not.
- This is called incomplete validation — one side of the fence is missing
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
- A product review has an ‘author’ field — a text parameter
- The browser sends it. The server stores it as the reviewer’s name.
- What if we put someone else’s email address in that field?
- If the server does not verify it against our session, the review appears as theirs
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
- FIND: spot any value your browser sends that the server acts on directly
- FIND: try zero, negatives, huge numbers — test ALL edges, not just the obvious ones
- FIND: in text fields, substitute someone else’s name or ID
- FIX: validate inputs on the SERVER, for EVERY range and direction
- FIX: for identity fields — ignore what the browser sends, use the verified session
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 test apps you own or have written permission to test
- Juice Shop is built for exactly this — run it locally to practice safely
- O-WASP tracks this under A-03 Injection and A-04 Insecure Design
- Coming next — Part 5: Authentication Bypass
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.