Vulnerability

Parameter Tampering
detect, understand, remediate

Parameter tampering is the manipulation of values that travel between client and server (hidden fields, query strings, POST bodies, cookies, and headers) to make the server act on values it should never have trusted. The best-known shape is shopping-cart price tampering, but the class spans privilege flips, identifier swaps, currency switches, quantity overflows, and feature-flag toggles. The bug is not the manipulation; it is the server choosing to trust a client-controlled value.

No credit card required. Free plan available forever.

Severity

High

CWE ID

CWE-472

OWASP Top 10

A04:2021 - Insecure Design

CVSS 3.1 Score

7.1

What is parameter tampering?

Parameter tampering (CWE-472) is the manipulation of values that travel between client and server in a way the server was not designed to anticipate. The values can sit in a hidden form field, a query string, a POST body, a cookie, a JSON request, a custom header, or a websocket frame. The class covers any case where the application accepts a client-controlled value and acts on it without rechecking that the user is authorised to set that value to that target.

The textbook example is the shopping cart that sends the unit price as a hidden form field at checkout. A user who edits the field before submission ends up paying whatever they typed. The same pattern shows up far beyond ecommerce: a quantity field that lets the user request more than their account quota allows, an account identifier in a profile update endpoint that lets the user write to another user's record, a feature flag in a request that flips premium features on, a currency code that converts a 100 USD charge into a 100 IDR charge.

Parameter tampering sits next to but is distinct from mass assignment, which covers automatic binding of arbitrary fields into a model, and insecure direct object reference, which covers identifier swaps that bypass authorisation. Tampering is the broader class: any client-controlled value the server should not have trusted, regardless of whether the framework auto-binds the field or the developer reads it explicitly. The OWASP Web Security Testing Guide places this under WSTG-BUSL-04 and WSTG-INPV-04 because the bug surfaces both as a logic failure and as an input-validation failure depending on how the application uses the value.

How parameter tampering plays out on a pentest

1

Map every parameter the client sends

The pentester proxies the application, walks the workflows, and records every form field, query parameter, header, cookie, and JSON property the client sends. Hidden fields and JSON properties are the high-yield targets.

2

Classify each parameter by trust

For each parameter the tester asks: should the server trust the client to set this value, recompute it from server-side state, or look it up from session context? Anything that should be server-side but is sent by the client is a candidate.

3

Mutate and replay

The tester edits each candidate (price to 0, quantity to negative, role to admin, currency to a low-rate currency, account_id to another user, flag to enabled) and replays the request. The server response says whether the value flowed through unchecked.

4

Walk the impact

A tampered value that landed in a database row, an authorisation decision, a billing calculation, or a feature-gate check converts from "interesting response" to "confirmed finding" with a reproducible proof of concept attached.

The most common parameter-tampering findings

The patterns below cover the majority of tampering findings on web, API, and mobile-backend engagements. Each one is a recognisable shape with a recognisable fix, and each one shows up across frameworks because the underlying bug is the same: the server accepted a client-controlled value where it should have used server-side state.

Price or amount in the checkout request

The client sends the price, the discount, or the currency code as part of the order submission. The fix is to derive every billable value from a server-side product record and a server-side rate table; the client sends only the SKU and the quantity.

User or account identifier in profile, settings, or admin endpoints

A profile update endpoint that takes a user_id or account_id parameter, or an admin endpoint that takes a target_user_id, lets the caller write to records they do not own. The fix is to read the actor from the session and reject any client-supplied identifier that does not match.

Role, plan, or permission flag in a request body

role=admin, plan=enterprise, is_verified=true, or feature_flag.* sent by the client and persisted by the server. The fix is to never accept these fields from the client; they live behind admin endpoints with their own authorisation.

Quantity or numeric value without bounds

A negative quantity that credits the account, a quantity larger than the user's quota, or a value that triggers integer overflow in downstream arithmetic. The fix is type and range validation on the server and a refusal to perform arithmetic on values outside the contract.

Hidden form field carrying state

A multi-step form that round-trips intermediate state through hidden fields (selected plan, computed discount, validated step) lets the user skip steps or alter the state between them. The fix is to keep multi-step state on the server keyed by a session token and to ignore hidden fields the client should not have touched.

Cookie carrying authorisation state

A cookie like role=user or tier=free that the server reads back as authoritative. The fix is to authenticate the user and look up role and tier from the database every request; the cookie holds only the session identifier.

Boolean or status enum that bypasses validation

is_admin=true, payment_status=paid, kyc_passed=true sent by the client and trusted by the server. The fix is to recompute these values from the underlying records that are supposed to govern them; the client never asserts them.

Foreign key in a relationship endpoint

An endpoint that creates a child record (a comment, an order line, a sub-task) and accepts the parent identifier from the client without checking ownership. The caller can attach records to a parent they do not own. The fix is to verify ownership of the parent before writing the child.

A worked example: hidden price field at checkout

A common shape on retail engagements is a checkout form that submits the order with a hidden field carrying the unit price. The frontend computed the price from the product page; the backend stores whatever the form sent.

<!-- Vulnerable: client-controlled price reaches the order record -->
<form action="/checkout" method="POST">
  <input type="hidden" name="sku" value="SKU-9921" />
  <input type="hidden" name="unit_price" value="129.00" />
  <input type="number" name="quantity" min="1" value="1" />
  <button type="submit">Place order</button>
</form>
# Vulnerable: trusts the client-supplied unit_price
@app.post("/checkout")
def checkout(req: CheckoutRequest):
    total = req.unit_price * req.quantity
    create_order(user=current_user(), sku=req.sku,
                 unit_price=req.unit_price, total=total)
    charge(current_user(), total)

The pentester opens devtools, edits the unit_price field to 1.00, submits the form, and observes that the order persists at the new price and the charge succeeds at the new amount. The proof is the order record (with the tampered price), the charge receipt (at the tampered amount), and the request and response captured in the proxy. The fix is to ignore the client-supplied price entirely and look the unit price up from the product table at order creation time, with the SKU as the only client-controlled input that influences the charge.

# Fixed: server derives unit_price from a server-side record
@app.post("/checkout")
def checkout(req: CheckoutRequest):
    product = product_table.get(req.sku)
    if product is None or not product.is_active:
        raise HTTPException(404, "Unknown SKU")
    if req.quantity < 1 or req.quantity > MAX_QTY:
        raise HTTPException(400, "Bad quantity")
    total = product.unit_price * req.quantity
    create_order(user=current_user(), sku=req.sku,
                 unit_price=product.unit_price, total=total)
    charge(current_user(), total)

On a SecPortal engagement, the finding is recorded against the affected endpoint with the proxied request and response, the order record, and the charge receipt as evidence. The CVSS vector reflects the impact: confidentiality is partly affected because the order record reveals the tampered structure, integrity is fully affected because the order amount no longer matches the catalogue, and availability is unaffected. The retest verifies that the fixed endpoint refuses to honour a tampered unit_price and that the catalogue lookup happens before any arithmetic.

How to detect parameter tampering

Automated detection

  • SecPortal's authenticated scanner probes parameters that influence pricing, identifiers, roles, and feature flags by mutating values and replaying the request, then compares responses to detect when the server accepted a value it should have rejected.
  • The code scanner flags handlers that read sensitive request fields (price, role, account_id, is_admin) without a downstream authorisation check or a server-side lookup, and surfaces the source location alongside the request shape.
  • SCA dependency analysis identifies frameworks and ORM versions where parameter binding defaults are unsafe (older Rails strong-parameters bypasses, deserialisation libraries that expand client JSON into model fields), so the workspace can prioritise endpoints that inherit those defaults.
  • OpenAPI and GraphQL schema scanning compares declared parameters to declared authorisation; fields marked as inputs but not declared as ownership-checked surface as draft findings the tester can confirm or dismiss.

Manual testing

  • Walk the application with two accounts (a low-privilege account and an admin account), capture every request, and diff the parameter sets. Fields that only the admin sends are candidates for vertical privilege escalation when set on a low-privilege request.
  • For every form, identify hidden fields and ask whether the server should be deriving them or trusting them. Edit them in-flight and resubmit; the response often answers the question directly.
  • For every JSON endpoint, attempt fields the API documentation does not list (role, is_admin, plan, verified_at, organisation_id) and observe whether the server stores them. Frameworks that auto-bind unknown fields are particularly susceptible.
  • Mutate numeric values to negative, zero, very large numbers, and decimal where integer is expected. Look for cases where the arithmetic still runs and the database accepts the result; those are the bounds-checking gaps that tampering exploits.
  • Mutate cookies that carry role, tier, or feature data (where present) and replay. Servers that read these cookies as authoritative without re-querying the user record fail this check immediately.

How to fix parameter tampering

Treat every client-controlled value as untrusted, every request

A field that arrives in a request is an input, not a fact. Re-derive sensitive values from server-side state on every request, and refuse to act on a client value where a server lookup is possible. The discipline applies regardless of whether the field came from the same form the server rendered seconds earlier.

Resolve identifiers from session, not from request body

The acting user, the owning account, and the tenant scope come from the authenticated session. A user_id, account_id, or tenant_id field in the request body is either redundant (the server already knows it) or a tampering opportunity (the server uses the client-supplied value). Remove the field or reject any value that does not match the session.

Look up authoritative data on the server

Prices, exchange rates, plan limits, role definitions, and tax rates live in server-side tables. Do not let the client send them. The client sends the SKU; the server looks up the price. The client sends a plan upgrade request; the server looks up the new entitlements. The client never asserts a fact the server is supposed to govern.

Validate types, ranges, and enums on every input

Type checks (integer, decimal, UUID, enum) and range checks (positive, bounded, within an allow-list) catch the most common tampering shapes before they reach the business logic. A schema validator (Pydantic, Zod, JSON Schema, Joi) is the right place to enforce them; ad-hoc if-statements drift over time.

Authorise every write on the resolved resource

After the server resolves the resource the request targets, run an authorisation check against the actor and the resource. The check answers the question: is this user allowed to perform this action on this object? Tampering becomes inert because the resolved resource is the one the server actually checks, not the one the client tried to substitute.

Never trust hidden fields with server-side meaning

A hidden field that round-trips state through the client lets the client edit the state. If the application needs intermediate state across a workflow, store it on the server keyed by a session or a workflow token; the client receives only an opaque token, not the state.

Log the resolved values, not the requested values

Audit logs that record the requested price, the requested role, or the requested account id make tampering investigations harder because the log shows the manipulation instead of the outcome. Log the resolved values: the price the server computed, the role the server confirmed, the account the server authorised. Investigations gain a reliable record of what the system actually did.

Reporting a parameter-tampering finding

Parameter tampering findings are easy to dispute when the report shows the tampered request without showing the tampered outcome. Engineering pushes back that "the price field is just a display value" or "the role parameter is whitelisted somewhere", and the finding stalls. The strongest reports name the exact endpoint, the parameter, the original and tampered values, the server response, and the durable artefact the tampered value produced (an order record, a database row, a charge receipt, a privilege change in the user table).

On a SecPortal engagement, the finding sits on the engagement record with the affected endpoint, the CVSS 3.1 vector calibrated to the actual impact (price tampering on a checkout differs from role tampering on a profile), the CWE-472 mapping (or CWE-639 for tampering that surfaces as authorisation bypass), the request, response, and post-tamper state evidence, and the remediation guidance from this page. Pentest engagement records keep the proof linked to the original commit and the retest verification, so the close-out conversation references the actual server-side fix rather than a vague "tampering addressed" ticket. The finding triage workflow covers how to separate scanner-derived candidates (a hidden field carrying a price) from manually validated findings (an order placed at a tampered amount) so the report differentiates suspected exposure from confirmed exploit.

Compliance impact

A pentester checklist for parameter tampering

The list below is the minimum coverage a tester should walk before declaring a target's request surface acceptable. Each item maps to a specific finding shape, with a CVSS profile that reflects the resource the parameter influenced and the data the tampering reached.

  • Inventory every form, every JSON endpoint, every GraphQL mutation, and every websocket frame. Record the parameters the client sends and classify each by who should govern the value.
  • Test every checkout, refund, cancellation, and billing endpoint by tampering price, currency, discount, and tax fields. Confirm the persisted order record matches the catalogue, not the request.
  • Test every user, account, and admin endpoint by substituting the actor identifier with another user the tester controls and another user the tester does not. Confirm the server resolves the actor from the session.
  • Test every role, plan, entitlement, and feature-flag field by sending elevated values from a low-privilege session. Confirm the server rejects or ignores the field rather than persisting it.
  • Test numeric and boolean inputs at boundary and out-of-range values: negative quantities, zero amounts, very large numbers, decimals where integers are expected, and unexpected booleans on validation flags.
  • Mutate cookies that carry application state (role, tier, feature flags) and replay. Servers that read those cookies as authoritative fail immediately; servers that re-query the user record on every request pass.
  • Record the CVSS vector calibrated to the resource the tampering influenced, the CWE mapping (CWE-472 for client-side parameter trust, CWE-639 when the bug surfaces as authorisation bypass), the request and response evidence, and the post-tamper state so the finding is reproducible at retest.

Catch parameter tampering before checkout ships

SecPortal probes hidden fields, query strings, and cookies for client-side trust patterns, flags missing server-side authorisation in source, and keeps the request and response evidence attached to the finding through retest. Start free.

No credit card required. Free plan available forever.