How to Audit Drupal Commerce Orders for Fraud
Audit Drupal Commerce orders for fraud after CVE-2026-118539: check for unpaid orders, false paid totals, price overrides and added adjustments.
To audit Drupal Commerce orders for fraud, upgrade first, preserve your logs, then search for four patterns: placed orders with no payment, orders marked paid with no payment records, prices no promotion explains, and adjustments your staff did not add. Confirm each hit against web server logs and your payment provider. This guide follows the checks Centarro published after the Drupal Commerce CVE-2026-118539 exploitation reports.
Before you audit Drupal Commerce orders
Do these three things first.
- Confirm what is deployed. Check the installed
drupal/commerceversion on the running site, not only the repository branch. The fixed releases are 3.3.13 and 8.x-2.41. - Upgrade if needed. New abuse during the audit would make your results stale immediately.
- Preserve evidence. Copy web server access logs and the Drupal log before rotation removes them. Log retention sets the practical limit on how far back you can confirm a suspicious order.
Run the queries on a read replica or a restored backup if you have one. Everything below only reads data, but long queries on a busy store database can still slow checkout.
What should you look for in a Drupal Commerce audit?
Look for orders whose payment state, prices or adjustments do not match what your store's normal checkout and promotions would produce. Centarro's rubric covers four patterns of increasing sophistication and notes that the list is not exhaustive.
You also need to know your own store here. A site that sells free items, takes donations or lets staff place orders will produce legitimate hits on every one of these checks.
Check 1: orders placed outside checkout with a balance due
Checkout places an order when it moves to the complete step, so an order placed through normal checkout has that value in checkout_step. Centarro says the exploit places the order directly and leaves the step unchanged. This query is reformatted from its maintainer post:
SELECT o.order_id, o.order_number, o.uid, o.mail, o.ip_address, o.state,
FROM_UNIXTIME(o.placed) AS placed, o.checkout_step,
o.total_price__number, o.total_paid__number, o.balance__number
FROM commerce_order o
WHERE o.placed IS NOT NULL
AND o.state NOT IN ('draft', 'canceled')
AND (o.checkout_step IS NULL OR o.checkout_step <> 'complete')
AND o.balance__number > 0
ORDER BY o.placed DESC;
Expect legitimate results: orders placed by staff, orders from Commerce API or custom flows, and orders placed by a payment gateway webhook with only an authorized payment. The queries in this guide use MySQL functions such as FROM_UNIXTIME, so adapt them if your site runs on another database.
Check 2: amount paid exceeds recorded payments
Drupal Commerce calculates the total paid by summing completed payment amounts. An order showing more paid than its payment records support had that value set some other way. Check 1 misses these orders because their balance is zero. Centarro says this mainly applies to sites that allow updates through JSON:API.
SELECT o.order_id, o.order_number, o.mail, o.ip_address, o.state,
FROM_UNIXTIME(o.placed) AS placed, o.total_paid__number,
COALESCE(SUM(p.amount__number - COALESCE(p.refunded_amount__number, 0)), 0) AS paid_by_payments
FROM commerce_order o
LEFT JOIN commerce_payment p ON p.order_id = o.order_id AND p.completed IS NOT NULL
WHERE o.placed IS NOT NULL AND o.total_paid__number > 0
GROUP BY o.order_id
HAVING o.total_paid__number > paid_by_payments
ORDER BY o.placed DESC;
There is a known blind spot. Recording a payment on the order later recalculates the total paid, so this query can miss an order that has had payments since it was altered.
Check 3: overridden unit prices
A price lowered and then paid through checkout passes both checks above. Prices entered by hand set the overridden_unit_price flag on the commerce_order_item table, and normal shopping does not. Filter that table for a value of 1 and join it to commerce_order to see the customer account and IP address. Centarro's post has the full query.
Legitimate hits include staff price overrides and "name your own price" or donation products. In the admin interface, the same signal is the "Override the unit price" checkbox on an order item. Do not limit your review to very low prices: the reports included orders at normal prices as well.
Check 4: manually added adjustments
Discounts, fees and other adjustments that your staff did not add and no promotion produced are the fourth signal. Filtering on adjustment type is not enough, because the widget allows any type. Centarro says every adjustment added through the widget has the source ID custom, stored in serialized form in commerce_order__adjustments and commerce_order_item__adjustments. Its post has a query that searches both tables. Staff adjustments will also appear, so compare hits with who actually worked on the order.
How do you confirm a suspicious order?
Match the order's IP address and time against your access logs and check that it passed through the normal checkout paths. Centarro calls this the best available confirmation.
Those paths begin with /checkout/.
Then check the money outside Drupal. Your payment provider's dashboard is a separate record that this vulnerability does not touch. An order marked paid in Commerce with no matching provider transaction is a strong signal, and the provider can also confirm whether a real payment matches the order total.
Check the other entities too
Orders were the reported target, but Ostorlab's patch analysis covers more of the data model. On marketplace or multi-vendor sites, or anywhere staff have limited Commerce roles, add these reviews:
- Product variations: changes to
product_idthat moved a variation to a product owned by a different account. - Coupons and promotions: coupons whose promotion differs from the one that issued them, and promotions holding coupons they were never assigned.
- Redemptions: coupon use under an unrelated promotion.
- API logs: JSON:API and REST writes to commerce entities from limited accounts. Ostorlab says failed attempts may log a 403 or 422, while successful changes on affected versions may log a 2xx.
- Deletions: promotion or product deletions that removed entities belonging to someone else.
What to do with confirmed fraud
Work through these in order:
- Hold any unfulfilled order on the list, and stop related shipments or license activations if you still can.
- Record the order, the account, the IP address, the log lines and the provider's transaction state in one place. Do this before editing the order, because later changes can recalculate totals.
- Review the accounts involved and every other order placed from them.
- Involve finance, and legal if needed, on recovering goods or reporting the loss.
- If the behavior does not fit the published flaw, report it through Drupal's confidential security process. Centarro says it would rather review false positives than miss a new vulnerability.
Keep the checks running
Exposure potentially dates to 2020 or 2021, depending on your setup, so a clean first pass over recent orders may need extending. Turn the first two queries into a scheduled report that goes to someone who knows the store's normal behavior. To reduce future exposure, switch JSON:API to read-only if your site does not need write operations, review how field-level authorization works with your developers, and add a payment check before fulfillment.