Client-side price tampering — modifying hidden form fields, JSON request bodies, or cart totals to purchase products at attacker-controlled prices.
TL;DR
price without catalog lookup.item.price with db.products.get(item.product_id).price.Price manipulation is a business logic vulnerability in which an application accepts a monetary value — price, unit_price, amount, total, or subtotal — directly from a client request and uses that value as the authoritative cost of a transaction. Because the value is supplied by the client, an attacker can intercept the request in a proxy, change price=49900 to price=1, and complete the purchase at one cent. The server never re-validates the value against its own product catalog. This is catalogued as CWE-840 (Business Logic Errors) with a contributing CWE-20 (Improper Input Validation) weakness, and falls under OWASP A04:2021 — Insecure Design.
The vulnerability is invisible to traditional injection scanners. No payload causes a 500 error. No regex matches a malicious string. The request is syntactically and semantically valid — it just happens to contain a number the developer never intended to be writable. Detection requires reasoning about the intent of each field, which is why DAST tools and WAFs both miss it. Manual penetration testers and business-logic-aware scanners catch it by intercepting the checkout flow and modifying numeric fields one at a time.
The financial exposure scales with order volume. A single undetected flaw on a high-traffic e-commerce platform can produce six-figure losses before reconciliation surfaces the discrepancy. This is why the HackerOne Business Logic top-payout list consistently places price manipulation in the top five most rewarded subtypes, and why Shopify-context disclosures have historically earned $25,000 to $150,000 depending on whether the flaw affects a single store or platform-wide checkout.
The exploit relies on a single broken trust boundary: the server delegates pricing authority to the client. The flow is identical across hidden-form, REST, and GraphQL stacks.
The attack collapses to four deterministic steps:
unit_price, never querying its own product catalog.The canonical vulnerable request, captured against a real e-commerce stack:
POST /api/cart/add HTTP/2
Host: shop.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGc...
Cookie: session=abc123
{
"product_id": "jacket-heavyweight-001",
"quantity": 1,
"price": 1
}A vulnerable backend responds with {"cart_id": "cart_xyz", "line_total": 1, "currency": "USD"} and proceeds to charge one cent for a $499 jacket. The Authorization header is valid — the attacker is an authenticated customer — which is precisely why scanners scoring requests on HTTP semantics see nothing wrong.
The price field is often added during dynamic-pricing features (bundles, member discounts, geo-pricing) and never removed when the feature is rolled back. Audit every monetary field in your cart and order schemas — if it is writeable, it is exploitable.
Five distinct techniques produce the same outcome; defensive controls must cover all five.
| Variant | Technique | Impact |
|---|---|---|
| Hidden form field edit | DevTools rewrite of <input type="hidden" name="price"> before submit | Critical — purchase at $0.01 |
| Burp intercept (REST/GraphQL) | Modify price in JSON body or GraphQL unitPrice argument | Critical — purchase at attacker price |
| Negative price/quantity | Set price=-500 or quantity=-1 for net-credit cart total | Critical — store credit issued |
| Integer overflow | Submit quantity=9223372036854775807 (INT64_MAX), wraps to negative total | High — bypasses >= 0 checks |
| Cart total override | POST {"cart_total": 0.01} to checkout endpoint that skips re-summing | Critical — entire order at $0.01 |
| Currency confusion | Pay in INR while server treats raw number as USD | High — 80x price reduction |
| Signed-payload replay | Replay an old signed cart payload after the catalog price increased | Medium — bounded by old-price delta |
The negative-quantity variant deserves special attention. Many backends check price >= 0 but never check quantity >= 1, so an attacker keeps a $1,000 item at quantity -1 while another low-cost item carries the order, and the arithmetic produces a negative total that the payment processor interprets as a refund obligation. Integer overflow is the inverse: positive values that mathematically wrap negative, defeating naive validation.
The following disclosures establish that price manipulation is a present, paid-out, multi-platform issue — not a theoretical concern.
HackerOne #218748 — Parameter tampering on e-commerce XML checkout — An attacker intercepted the XML checkout payload, modified the <price> element, and completed purchases at attacker-chosen prices. Severity: High. Bounty paid (amount undisclosed). The report demonstrates that XML-based stacks are equally exposed; the vulnerability class is transport-agnostic.
HackerOne #364843 — OLO total price manipulation via negative quantity — On the OLO food-ordering platform, setting quantity=-1 for a high-value item while keeping other items in the cart caused the backend arithmetic to subtract that item's cost from the order total. Attackers could order meals for free or receive net credit. The fix required validating quantity >= 1 at the API boundary, not just at the UI.
HackerOne #927661 — Payment platform amount tamperable by negative fraction — A payment processor accepted a fractional negative amount parameter, reducing the total owed. Bounty: $500 (Medium, capped because exploitability was bounded to small reductions). This illustrates that severity scales directly with how much an attacker can deviate from the legitimate price.
HackerOne #1562515 — Glovo integer overflow — After Glovo remediated an initial price-tampering report by removing the price field, a follow-up demonstrated that the remaining quantity field was vulnerable to integer overflow: setting quantity to 9223372036854775807 (INT64_MAX) wrapped the line total to negative. The lesson is that removing one field is insufficient when arithmetic still depends on client values.
HackerOne #1446090 — Krisp seat downgrade without revalidation — A PUT /subscription endpoint accepted a seats count lower than the already-provisioned count without re-validating against the billing tier. Customers could downgrade billing while retaining access to all provisioned seats — a price manipulation expressed through subscription state rather than a cart.
CVE-2025-56426 — Bagisto CMS cart price manipulation — Bagisto, a Laravel-based open-source e-commerce platform, accepted a price parameter in its add-to-cart API without server-side catalog lookup. Scored CVSS 8.1 High with vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H. Patched in Bagisto 2.2.3 in April 2025. The pre-patch code is a textbook example: the controller passed request()->price directly into the cart-line constructor.
PortSwigger Web Security Academy publishes three reference labs that mirror these patterns: Excessive trust in client-side controls (modify price for the leather jacket), Low-level logic flaw (32-bit integer overflow on line totals), and Infinite money logic flaw (gift-card discount stacking). The labs are useful for training pentesters because the exploit is identical to the production cases above.
Burp Suite (or Caido) is the standard tool. The methodology is procedural and reproducible:
POST /cart/add (or equivalent), search the request body for any numeric field whose value matches the displayed price — common names: price, unit_price, amount, cost, total, subtotal, unitPrice.document.querySelectorAll('input[type=hidden]') from the browser console catches legacy patterns.1, then 0.01, then -9999, sending each variant via Burp Repeater. Always test a value that differs by exactly 1 (4989 vs 4990) — subtle changes pass format validation while still proving the trust issue.confirmed — financial state change verified.Repeat the procedure for the checkout endpoint itself. Some applications correctly validate prices at /cart/add but accept a client-supplied cart_total at /checkout/confirm, skipping the line-item re-sum. Both surfaces must be tested independently.
Generic DAST scanners (ZAP, Burp Active Scan, Acunetix) miss this class because no payload triggers an error. Specialized business-logic detection requires:
^(unit_)?price|amount|cost|total|subtotal$ followed by automated mutation and response diffing.Limitations: scanners that lack authenticated session handling cannot reach the cart endpoint. Scanners that lack stateful crawl cannot correlate the displayed price with the request body field. False positives arise on legitimate dynamic-pricing endpoints (bundle discounts, B2B contract pricing) where price is a valid input from a privileged caller.
BreachVex detects this through its business-logic engine, which crawls the cart and checkout flows with an authenticated session, identifies numeric fields whose values match catalog prices, and asserts integrity by submitting tampered variants and verifying that the order total tracks the modified input rather than the catalog value.
The canonical fix is one rule: never read the price from the request. Always look it up by product_id from the authoritative catalog. Below is a side-by-side comparison in FastAPI; the Express equivalent is identical in shape.
Vulnerable Python (FastAPI):
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
router = APIRouter()
class CartItem(BaseModel):
product_id: str
quantity: int
price: float # BUG: writeable from client
@router.post("/cart/add")
async def add_to_cart(item: CartItem, db=Depends(get_db)):
cart_entry = CartEntry(
product_id=item.product_id,
quantity=item.quantity,
unit_price=item.price, # <-- attacker-controlled
)
db.add(cart_entry)
db.commit()
return {"line_total": item.quantity * item.price}Fixed Python (FastAPI):
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
router = APIRouter()
class CartAddRequest(BaseModel):
product_id: str
quantity: int = Field(ge=1, le=100)
# price field deliberately omitted
@router.post("/cart/add")
async def add_to_cart(item: CartAddRequest, db=Depends(get_db)):
product = db.query(Product).filter(Product.id == item.product_id).first()
if not product:
raise HTTPException(status_code=404, detail="Product not found")
cart_entry = CartEntry(
product_id=item.product_id,
quantity=item.quantity,
unit_price=product.price, # <-- authoritative catalog price
)
db.add(cart_entry)
db.commit()
return {"line_total": item.quantity * product.price}Two structural details matter beyond the catalog lookup. First, CartAddRequest does not declare a price field — Pydantic will reject extra fields if model_config = ConfigDict(extra='forbid') is set, eliminating mass-assignment surprises from ORM serializers. Second, quantity is bounded by Field(ge=1, le=100) to block negative values and integer-overflow attempts at the API boundary, before any arithmetic runs.
A second control layer protects against logic flaws elsewhere in the cart pipeline. Even if every add request was correctly priced, recompute the order total at checkout from the catalog — never trust the cart table's stored unit prices and never accept a client-supplied cart_total.
@router.post("/checkout/confirm")
async def confirm_order(order_id: str, db=Depends(get_db)):
cart_items = db.query(CartEntry).filter(
CartEntry.order_id == order_id
).all()
calculated_total = sum(
db.query(Product).get(item.product_id).price * item.quantity
for item in cart_items
)
# Always recompute; never trust submitted totals
charge_payment(amount=calculated_total)For stateless architectures where the cart lives client-side or in a CDN edge cache, generate a server-side HMAC of the cart contents at the moment prices are resolved, and re-verify the signature at checkout:
import hmac, hashlib, json, os
SECRET = os.environ["CART_SIGNING_KEY"]
def sign_cart(cart: dict) -> str:
payload = json.dumps(cart, sort_keys=True)
return hmac.new(
SECRET.encode(), payload.encode(), hashlib.sha256
).hexdigest()
def verify_cart(cart: dict, signature: str) -> bool:
return hmac.compare_digest(sign_cart(cart), signature)Any modification to quantities, prices, or product IDs invalidates the signature, and the checkout endpoint refuses the order. The signing key must rotate independently of session secrets and live in a secrets manager — never in source control.
Store monetary values as integer cents, never as float. Floating-point arithmetic introduces precision drift that attackers exploit (0.1 + 0.2 != 0.3) and integer overflow vulnerabilities like Glovo's INT64_MAX wrap depend on signed-int arithmetic — choose unsigned integer types or constrain ranges explicitly.
qty=-1 or integer overflow.Price manipulation is a business logic flaw in which an application accepts a monetary value (price, unit_price, amount, total) directly from a client request and uses that value as the authoritative cost. Attackers intercept the request in Burp Repeater and change price=49900 to price=1, completing the purchase at one cent. Mapped to CWE-840 (Business Logic Errors) with contributing CWE-20 (Improper Input Validation), and OWASP A04:2021 (renamed A06:2025 — Insecure Design). CVE-2025-56426 (Bagisto v2.3.6 cart price manipulation) is the canonical 2025 example.
Three primary methods. (1) Edit hidden form fields via browser DevTools before submit (legacy pattern). (2) Intercept POST /api/cart/add in Burp Proxy and modify the price field in the JSON body. (3) Manipulate cart_total or subtotal in /checkout/confirm if the server skips re-summing line items. Real cases: HackerOne #364843 (OLO/Upserve negative quantity reduces order total), HackerOne #1562515 (Glovo integer overflow on order price), HackerOne #218748 (Adobe parameter tampering). All exploit server-side trust in client values.
Price manipulation modifies a client-supplied total directly (set price to zero, send -1 quantity) — fixed by recomputing totals server-side from a trusted catalog. Discount stacking sends the correct coupon code with the correct user identity but redeems it more times than intended via TOCTOU race conditions on the coupon table — fixed by atomic transactions and idempotency keys. The two often coexist (apply discount then manipulate price), but the controls are distinct: server-side recomputation does not protect against race conditions, and locking does not protect against client value substitution.
Five-layer defense. (1) Server-side catalog lookup: never accept price from the client; always SELECT price FROM products WHERE id=? on every cart action. (2) Pydantic with extra='forbid' or zod .strict() to reject unexpected fields. (3) Quantity bounds: conint(ge=1, le=999) for items, condecimal(gt=0) for amounts. (4) HMAC-signed cart tokens so the cart contents cannot be edited between view and checkout. (5) Use integer cents (DECIMAL(10,2) or BIGINT) — never IEEE 754 floats for money. Stripe and Shopify both enforce all five.
Generic DAST scanners (ZAP, Burp Scanner, Nuclei, Acunetix) cannot reliably detect price manipulation because there is no malicious payload to match — the request looks like a normal purchase, just with a different number. Detection requires understanding what a price 'should' be, which means correlating the request with a product catalog and known price ranges. Specialized business logic tools (Pynt, Escape BLST, StackHawk BLT launched December 2025) and BreachVex's business-logic detection attempt this via per-product baseline comparison and out-of-range detection.
CVE-2025-56426 (Bagisto CMS v2.3.6, CVSS 6.5, Laravel/PHP) is the flagship 2025 case for cart price manipulation in a major open-source e-commerce stack. WordPress price-manipulation CVEs typically come via WooCommerce extensions and are tracked by Patchstack rather than NVD. Shopify-side cases route through the private HackerOne program with bounty payouts in the $25K–$150K range for full checkout total bypass. CVE-2020-11007 (Shopizer Java Spring) covers the negative-quantity variant — closely related but technically a different CWE.
Price manipulation is a specific subtype of parameter tampering applied to monetary fields. Parameter tampering is the general class — modifying any client-controlled parameter (user_id for IDOR, role=admin for privilege escalation, price=1 for free orders). Price manipulation focuses on the financial impact subset where the modified parameter directly determines transaction cost. The OWASP WSTG covers both under WSTG-BUSL-05 and WSTG-INPV-04 respectively. Defense for price manipulation is server-side catalog lookup; defense for general parameter tampering is server-side authorization and validation.