Submitting negative or overflowing quantity values to invert order arithmetic — turning a purchase into store credit or zeroing the cart total.
TL;DR
price × quantity arithmetic, producing a negative subtotal that zeros or credits the cart.qty=-1), negative financial transfers (amount=-500), and integer overflow wrap-around (qty=2^63) that bypasses naive minus-sign filters.quantity > 0 server-side with typed validation and a database CHECK constraint as defense-in-depth.Negative Quantity Abuse is a business-logic flaw in which an application's order, cart, or financial transfer system fails to enforce that quantity or amount inputs are strictly positive. Because the server computes total = price × quantity, a negative quantity yields a negative subtotal — and in many cart implementations, the grand total drops below zero, generating an effective credit to the attacker. The root cause is CWE-20: Improper Input Validation, frequently compounded by CWE-190: Integer Overflow or Wraparound when the attacker bypasses minus-sign filters by submitting values that exceed the signed-integer range.
This vulnerability is sometimes confused with price manipulation, but the attack surfaces are distinct. Price manipulation tampers with the value of an item (e.g., changing unit_price from $499.99 to $0.01). Negative Quantity Abuse instead tampers with the sign of the multiplier — leaving the price untouched while flipping the arithmetic. The defenses also differ: price manipulation requires server-side price lookup, whereas negative quantity attacks require strict numeric range validation and database constraints. Both fall under OWASP A04:2021 — Insecure Design because they exploit logic the developer never intended to be reachable.
The vulnerability is particularly insidious because the attacker's input is technically a valid integer. A naive validator that accepts "any number" will pass the request through. Web Application Firewalls do not flag the request, type coercion succeeds, and the database INSERT executes without error. The only signal that something is wrong is the negative subtotal in the response — a signal that automated DAST scanners rarely interpret as a finding because they lack the business context to know that total: -218.00 is anomalous.
The attack surface extends far beyond e-commerce checkouts. Anywhere a numeric quantity flows into a multiplication, accumulation, or balance update — loyalty point redemption, gift card top-up, ride-share fare splitting, marketplace seller payout, ad-budget consumption, in-game currency exchange — the same arithmetic inversion applies. The vulnerability also intersects with CWE-682 (Incorrect Calculation) and CWE-840 (Business Logic Errors) in classification systems that track logic flaws separately from input validation, but the underlying defect and the defense are identical.
A vulnerable cart endpoint trusts the client-supplied quantity and pipes it directly into the arithmetic. The high-level flow of a single exploit interaction is shown below:
The arithmetic inversion only requires three ingredients: a numeric input the attacker controls, a multiplication that propagates the sign, and a persistence layer that accepts the result. The set of languages and ORMs that satisfy these conditions by default is enormous. PHP's loose int coercion, Java's primitive int and long, JavaScript's untyped Number, and Python class attributes without type hints all silently accept negative inputs. On the persistence side, SQLAlchemy Integer, Prisma Int, Sequelize INTEGER, Django IntegerField, ActiveRecord :integer, and TypeORM int accept any value within the column's signed range — including the entire negative half — unless the developer explicitly attaches a positive-bound validator. The practical result is that the default configuration of every mainstream web stack ships vulnerable to this class of attack, and only an intentional, layered defense closes the gap.
Three exploitation paths flow from this single root cause:
"quantity": 1 to "quantity": -1. The server computes price × -1 and subtracts the line item from the running total. Chaining this with high-value items zeros the basket or generates a negative balance that some platforms convert to store credit.POST /api/transfer, POST /api/refund, or POST /api/payment accept an amount field. Without sign validation, sending amount=-500 reverses the transfer semantics — crediting the attacker's account and debiting either the victim or the platform's float.+2,147,483,647 to -2,147,483,648 when incremented. Submitting qty=2147483648 (or 9223372036854775808 for int64) produces a legitimately negative quantity without ever sending a minus sign. This bypasses regex filters that only block the - character.A representative vulnerable request:
POST /api/cart/update HTTP/1.1
Host: shop.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGc...
{
"cart_id": "a3f9b2",
"items": [
{ "product_id": 101, "quantity": 1, "unit_price": 299.99 },
{ "product_id": 204, "quantity": -1, "unit_price": 499.99 }
]
}The vulnerable response confirms the inversion:
HTTP/1.1 200 OK
Content-Type: application/json
{
"cart_id": "a3f9b2",
"subtotal": -200.00,
"tax": -18.00,
"total": -218.00,
"message": "Cart updated successfully"
}The server has accepted a negative total. Depending on downstream handling, the checkout step will either complete at $0.00, refuse the order with an obscure error, or — worst case — issue a $218.00 store credit redeemable on a later order.
A regex such as ^-?\d+$ or a filter that strips the - character does not block integer overflow. Submitting qty=9223372036854775808 contains no minus sign and passes any character-based filter, yet wraps to -9223372036854775808 once cast to a 64-bit signed integer. Range validation, not pattern validation, is the only correct defense.
| Variant | Technique | Impact |
|---|---|---|
| Direct negative quantity | Set qty=-1 on a high-value line item | Zeroes or inverts cart total; may produce store credit |
| Quantity zero bypass | Set qty=0 to skip minimum-purchase rules | BOGO/bundle discount abuse, free promo unlock |
| 32-bit integer overflow | Submit qty=2147483648 to wrap signed int | Total drops to -INT_MIN × price, e.g., -$21,474,836 |
| 64-bit integer overflow | Submit qty=9223372036854775808 to wrap int64 | Catastrophic negative total; full refund credit |
| Negative amount transfer | POST /api/transfer with amount=-500 | Funds flow reversed; attacker credits own account |
| Negative refund replay | POST /api/refund with refund_amount=-100 | Refund inverted into additional charge, or oversized credit |
The 32-bit and 64-bit overflow variants are the most dangerous in legacy stacks. Java int, C# int, and MySQL INT columns silently wrap on overflow with no exception, no log entry, and no application-layer indication. PHP on 64-bit operating systems converts past PHP_INT_MAX to a float, which introduces precision errors but typically still produces a usable attack primitive in arithmetic-heavy code paths. Go's native int width is platform-dependent and wraps silently in release builds; Rust panics on overflow in debug mode but wraps in release unless the developer explicitly uses checked_mul or the overflow-checks = true profile flag. JavaScript escapes integer overflow because all numbers are IEEE 754 doubles, but loses integer precision above Number.MAX_SAFE_INTEGER (2^53 − 1), which can produce incorrect totals on large quantities even without sign inversion.
The negative refund replay variant deserves separate attention because it often slips past code review. A reviewer reading if (refund_amount > original_charge) reject() will conclude the comparison is sound — but the comparison passes when refund_amount = -100 (because -100 < original_charge), and the downstream ledger write applies the negative amount as an additional charge against the customer or as an inflated credit, depending on the sign convention of the accounting layer. The fix requires both refund_amount > 0 AND refund_amount <= original_charge as separate assertions, never combined into a single bounded comparison.
Negative-amount attacks against financial APIs (transfer, refund, withdrawal, ACH initiation) are not academic. Researchers testing open-banking endpoints in 2021 demonstrated unauthorized credit of attacker-controlled accounts via amount=-N on multiple production APIs. Combined with a BOLA/IDOR weakness on the source-account selector, the same flaw permits direct theft from third-party accounts. Treat every monetary endpoint as life-critical for sign validation.
CVE-2020-11007 — Shopizer (Java Spring e-commerce, < 2.11.0). Shopizer accepted a negative qty query parameter on its add-to-cart endpoint without server-side validation. The proof-of-concept is a one-line GET: GET /shop/cart/addToCart?merchantId=1&productId=43&qty=-240. The cart subtotal turned negative, and an attacker could open a second tab during active checkout to apply the manipulated total. CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N — scored 6.5 (Medium). The fix in 2.11.0 was a single-line backend check that resets qty to 1 whenever a negative value is detected.
CVE-2025-56426 — Bagisto CMS v2.3.6 (Laravel/PHP). The Bagisto e-commerce platform allowed an attacker to intercept a cart-update request and set one product's quantity to -1. The faulty calculation logic subtracted the item's price from the cart total, enabling checkout at $0 for goods worth hundreds of dollars. Beyond direct financial loss, the negative quantity corrupted inventory counters — items that should have been marked exhausted remained listed as available, causing fulfillment failures. CVSS v3 scored 6.5 (Medium).
CVE-2023-45854 — Shopkit 1.0. A textbook integer-overflow flaw in the add-to-cart functionality. An attacker submitting a specially crafted large quantity value triggered signed-integer wrap-around in the cart subtotal calculation, producing an unauthorized negative price. Classified as a critical vulnerability by the assigning authority because no authentication was required and the exploit was a single HTTP request.
Bug Bounty — major e-commerce platform (2021, disclosed via HackerOne). A researcher submitted an order containing 9223427036854775808 items (slightly above 2^63, the maximum value of a signed 64-bit integer plus one). PHP's internal integer handling wrapped the value to a negative number close to PHP_INT_MIN. The cart total computed to a large negative figure, and the platform's checkout pipeline accepted the order, issuing a refund credit worth thousands of dollars to the attacker's account. The researcher received a significant bounty; the patch capped quantity at 999 server-side. The incident is widely cited in business-logic training material as the canonical 2^63 overflow attack against modern e-commerce.
Open-banking API research (BusinessWire disclosure, 2021). Independent researchers testing a sample of production open-banking transfer endpoints demonstrated that several implementations accepted negative amount fields without validation. The reported attack chains included unauthorized credit of attacker-controlled accounts, theft from third-party accounts when chained with a BOLA/IDOR weakness on the source-account selector, and balance inflation without a corresponding deposit. The disclosure prompted multiple banks to push emergency hotfixes adding strict-positive amount validation across their public APIs.
Beyond the CVE-numbered cases, multiple bug bounty disclosures established that negative quantity is a recurring pattern across e-commerce platforms. HackerOne #364843 (Upserve/OLO, $250 Medium) — including a menu item with quantity: -1 reduced the total order price because the server validated the SKU but not the sign of the quantity. HackerOne #403783 (Zomato, $250 Medium) — a researcher changed the support_rider_amount field to a negative decimal, and the server accepted the value, reducing the order total on every order. HackerOne #1562515 (Glovo, undisclosed bounty) — integer overflow in the order price calculation produced a near-zero or negative cart total. These reports match the CVE-2020-11007 / CVE-2025-56426 / CVE-2023-45854 mechanism documented above and confirm the class is actively exploited at scale.
A focused Burp Suite session is the fastest way to confirm or refute the vulnerability.
POST /cart/add, PUT /cart/update, PATCH /cart/item/:id, POST /order/create, POST /checkout, POST /transfer, POST /payment, POST /refund. Any field named qty, quantity, amount, count, units, or volume is a candidate.POST /cart/add in Burp Proxy, and note the cart total in the response."quantity": 1 with "quantity": -1 in Repeater. Forward the request and inspect the response. If the cart total decreases or returns negative with HTTP 200, the endpoint is vulnerable.qty=0. If the item is added without satisfying the platform's minimum-quantity rule, you have a partial bypass even without arithmetic inversion.2147483647, 2147483648, 9223372036854775807, and 9223372036854775808. Watch for sign inversion in the response, an HTTP 500 (which leaks the integer type), or a sudden change in the rendered total."amount": -100 to transfer/refund/payment endpoints. Any 200 response that reports a balance change is a confirmed finding.[-1000, -100, -1, 0, 2147483647, 2147483648, 9223372036854775807, 9223372036854775808] and target every numeric field across the discovered endpoints. Sort results by response length and total field to find anomalies quickly.For the most thorough sweep, run a dedicated Intruder attack against every numeric parameter discovered in step 1. The wordlist [-1, 0, -0, 0.5, -999999999, 2147483648, 9223372036854775808, NaN, Infinity] exercises sign inversion, zero bypass, signed-zero regex evasion, fractional rounding, far-negative range, 32-bit overflow, 64-bit overflow, and the two JSON edge values (NaN and Infinity) that some parsers accept without error.
quantity (or amount, count, units, volume) and click Add §. Intruder will mutate only this position, keeping the rest of the body intact.-1, 0, -0, 0.5, -999999999, 2147483648, 9223372036854775808, NaN, Infinity. Disable URL-encoding for the payload characters so that JSON numeric tokens reach the server intact."total":\s*(-?\d+(?:\.\d+)?)). Intruder will surface the extracted value as a column in the result table, making sign inversion immediately visible./api/refund and /api/transfer. Financial endpoints often duplicate the same input handler as cart endpoints; if the cart fails, the financial endpoints almost certainly fail too.Generic DAST scanners struggle with this class of flaw for two reasons. First, the attacker's input is a syntactically valid integer, so input validation rules pass it through. Second, the vulnerability surfaces only in a downstream computation — the scanner would have to parse the response, locate the cart total, and recognize that a negative value is anomalous in that field. Most scanners do not maintain that level of business context.
The detection signals that do work are:
total field across baseline (qty=1) and attack (qty=-1) responses. If total_attack < total_baseline, the endpoint inverts arithmetic.qty=2147483648 whose total field has the opposite sign of the baseline is a high-confidence indicator.qty=2147483648 (without erroring on qty=2147483647) signals an INT type boundary — the column overflows but the application catches the exception. The endpoint is borderline-vulnerable depending on how the exception is handled.qty=-5 request that decreases the displayed stock of a product by -5 (i.e., increases stock) signals the same flaw on a different field.BreachVex detects this via typed numeric fuzzing on cart, order, and financial endpoints — submitting negative values, zero, and 32/64-bit overflow boundaries against every parameter named qty, quantity, amount, count, units, or matching project-defined heuristics, then comparing arithmetic deltas in the response body to flag sign inversion or anomalous totals. The scanner cross-references each response against a baseline captured from a known-good request, so a negative total in a field that was positive in baseline is flagged regardless of the field name or the surrounding response shape.
The defense is a layered combination of typed input validation at the API boundary and a database-level CHECK constraint as a fail-safe. Either layer alone is insufficient.
Validate every numeric field at the API boundary with both type coercion and range bounds. Two functional examples — first vulnerable, then fixed — for the two most common backend stacks.
Python (FastAPI + Pydantic) — vulnerable:
from pydantic import BaseModel
class CartItem(BaseModel):
product_id: int
quantity: int # No range check — accepts -1, 0, 2^63Python (FastAPI + Pydantic) — fixed:
from pydantic import BaseModel, Field, conint
class CartItem(BaseModel):
product_id: int = Field(..., gt=0)
quantity: conint(ge=1, le=999) # Strictly positive, capped business maxNode.js (Express + zod) — vulnerable:
import { z } from "zod";
const cartItemSchema = z.object({
product_id: z.number(),
quantity: z.number(), // Accepts negatives and overflow
});Node.js (Express + zod) — fixed:
import { z } from "zod";
const cartItemSchema = z.object({
product_id: z.number().int().positive(),
quantity: z.number().int().positive().max(999),
});The cap of 999 is a defense-in-depth heuristic: any legitimate retail order will be well below this number, and any input above it is treated as an attack. The exact ceiling is a business-policy decision, but a finite ceiling must exist to prevent overflow attacks on the underlying storage column.
For monetary fields, never use floating-point types. Use Decimal (Python), BigDecimal (Java), or NUMERIC (PostgreSQL) with explicit positive constraints:
from decimal import Decimal
from pydantic import BaseModel, condecimal
class TransferRequest(BaseModel):
from_account: str
to_account: str
amount: condecimal(gt=Decimal("0"), max_digits=12, decimal_places=2)Validation at the API boundary can be bypassed by internal services that write directly to the database, by SQL injection, or by mass-assignment flaws. A CHECK constraint at the column level closes that gap:
-- PostgreSQL: prevent negative quantities at the storage layer
ALTER TABLE order_items
ADD CONSTRAINT chk_quantity_positive CHECK (quantity > 0);
ALTER TABLE cart_items
ADD CONSTRAINT chk_cart_qty_positive CHECK (quantity > 0);
-- Prevent negative amounts in financial tables
ALTER TABLE transactions
ADD CONSTRAINT chk_amount_positive CHECK (amount > 0);The CHECK constraint causes an IntegrityError on any INSERT or UPDATE that would store a non-positive value, regardless of which application service initiated the write. Pair it with BIGINT or NUMERIC column types — never INT — for any quantity field that might receive overflow attacks. The cost of BIGINT over INT is four extra bytes per row; the cost of an unguarded INT is the entire merchandise inventory.
Database CHECK constraints are not a replacement for API validation — they fire too late in the request flow to return a useful error message to the client, and they incur the cost of a round-trip to the database. Use API-layer Pydantic/zod validation as the primary defense and the CHECK constraint as the fail-safe.
For Java and C# stacks specifically, prefer BigInteger / BigDecimal over native int/long for any quantity or amount field that crosses a trust boundary, and enable Math.addExact (Java) or checked { ... } blocks (C#) in the arithmetic itself. These produce explicit ArithmeticException on overflow rather than silent wrap-around, converting a stealthy logic bug into a loud runtime failure.
A complete defensive posture also includes anomaly detection on the order pipeline. Any order with a final total below a sane floor (for example, total < 0.50 × sum_of_unit_prices) should be quarantined for manual review before fulfillment, and any change in inventory counters that increases stock without a corresponding restock event should fire an alert. These controls catch the residual class of attacks where a legitimate-looking input combines with a logic flaw the validators did not anticipate — for instance, coupon-stacking interactions that drive the post-discount total negative even when each individual quantity is positive. Defense-in-depth at the order-acceptance and fulfillment layers turns a single missing validator into a recoverable incident rather than a refund-the-warehouse event.
-0) bypass shares the regex evasion pattern.Negative quantity abuse is a business-logic flaw where an attacker submits a negative integer (e.g., qty=-1) to a cart, order, or financial endpoint that fails to enforce a strictly-positive constraint. The server multiplies price by the negative quantity and produces a negative subtotal, which can zero the cart or generate store credit. Root cause is CWE-20 (Improper Input Validation), often compounded by CWE-190 (Integer Overflow) when attackers use 2^31 or 2^63 to wrap signed integers without ever sending a minus sign.
Signed integer types in Java, C#, MySQL INT, and PHP on 32-bit platforms wrap silently when a value exceeds their range. Submitting qty=2147483648 (one above INT_MAX) to a 32-bit column rolls the stored value to -2147483648, and the cart computes price × -2147483648, producing a catastrophically negative total. This bypasses naive minus-sign filters because the request body never contains a `-` character.
It works whenever the developer trusts the client-supplied quantity field and pipes it directly into the arithmetic without a Pydantic/zod/Joi range check or a database CHECK constraint. The input passes JSON parsing as a valid integer, the WAF does not flag it (no SQL or script characters), and the only signal of compromise is the negative subtotal in the response — a signal generic DAST scanners do not interpret as a vulnerability.
CVE-2020-11007 is a textbook negative quantity flaw in Shopizer (Java Spring e-commerce platform, < 2.11.0). The endpoint GET /shop/cart/addToCart?merchantId=1&productId=43&qty=-240 accepted a negative qty parameter without server-side validation, causing the cart subtotal to invert. CVSS 6.5 Medium. The fix in 2.11.0 was a single-line backend check that resets qty to 1 whenever a negative value is detected.
In Python with FastAPI use Pydantic conint(ge=1, le=999) or Field(..., gt=0, le=999) on the quantity field. In Node.js with Express use zod: z.number().int().positive().max(999). For monetary fields use Decimal/BigDecimal types with condecimal(gt=Decimal('0'), max_digits=12, decimal_places=2). Add a database CHECK (quantity > 0) constraint as defense-in-depth so internal services and SQL injection cannot bypass the API-layer validator.
Submitting qty=9223372036854775808 (one above 2^63 − 1, the maximum signed int64) wraps the stored value to -9223372036854775808 in Java long, C# long, PostgreSQL BIGINT, and MySQL BIGINT. A bug bounty researcher reportedly used 9223427036854775808 against a major e-commerce platform in 2021, triggering a thousands-of-dollars refund credit. The platform patched by capping quantity at 999 server-side.
Yes. Setting qty=0 on a line item often bypasses minimum-purchase, BOGO, or bundle-discount rules because the validator only checks the sign, not zero. The item is still attached to the cart for promotion-eligibility computation but contributes nothing to the total — unlocking free promo items or skipping minimum-spend gates. Always enforce qty >= 1 with Pydantic conint(ge=1) or zod .int().positive(), never .nonnegative() or .gte(0).