
This post is being published in consultation with the Drupal Security Team.This vulnerability impacts both full-stack and headless sites, with attack vectors that differ based on the site’s architecture.Reports included orders placed with obviously incorrect prices (e.g., Drupal Commerce calculates the total paid on an order by summing completed payment amounts. An order whose total paid is higher than that sum had it set some other way. Query 1 misses these orders, because their balance is zero.
Attempted exploits have been observed on live sites
We received two reports of attempts by malicious users to place fraudulent orders using a vulnerability patched in Commerce Core 3.3.13 and 8.x-2.41 (SA-CONTRIB-2026-254). Centarro and the Drupal Security Team looked for broader abuse of this issue. As of now we do not believe it was scripted or exploited beyond the initial sites.Finally, I’d personally like to thank Jonathan Sacksick, Tom Ashe, and Jakub Piasecki from Centarro for going the extra mile outside business hours to prepare solutions that solved the reported issues with minimal impact to the function of existing stores. If you lack internal resources to either apply updates or evaluate your site for exploits, our team maintains community and paid support channels to assist.
Identifying potential exploits
SELECT oi.order_id, oi.order_item_id, oi.title, oi.unit_price__number, FROM_UNIXTIME(oi.changed) AS changed, o.uid, o.ip_address, o.state FROM commerce_order_item oi JOIN commerce_order o ON o.order_id = oi.order_id WHERE oi.overridden_unit_price = 1 ORDER BY oi.changed DESC;
To determine if any of your recent orders were fraudulently placed or otherwise manipulated, evaluate them according to the following rubric organized according to exploits of increasing sophistication. Every Drupal Commerce site is unique, so you should evaluate the results of each item based on your knowledge of the legitimate behaviors your site supports.
advisory listing page to subscribe to be notified of security releases.
Giving thanks
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;
As the maintainers of Drupal Commerce, we are advising users to upgrade immediately to the recently published releases of Commerce Core to protect against a vulnerability that may have financial impact.
Using SQL to identify potential exploits
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;
Checkout places the order when it moves to the complete step, so every order placed through checkout has a checkout_step value of complete. The exploit places the order directly and leaves the checkout_step unchanged.Those who reported the issue were merchants who observed suspicious activity on live sites, not merely security researchers evaluating the platform for vulnerabilities. Given that merchants observed this behavior, we highly recommend every user evaluate recent orders to determine whether or not any were fraudulently placed.
Query 1: orders placed outside checkout with a balance due
This is not an exhaustive list. Additionally, there are legitimate reasons to have placed orders that do not have payments or that have overridden unit prices, so even with these queries, it’s still up to your judgment whether or not the results represent any actual exploits.We analyzed the issue in private with the Drupal Security Team, who coordinated the testing, packaging, and release of the module updates on October 28, 2026.
Query 2: amount paid exceeds recorded payments
The adjustment widget lets the user choose any adjustment type (custom, fee, promotion, tax, or other adjustment types defined by contributed or custom modules), so filtering on the type isn’t enough. Every adjustment added through the widget has the source ID custom, whatever its type. Adjustments are stored serialized.SELECT 'order' AS level, a.entity_id AS order_id, o.uid, o.ip_address, o.state, FROM_UNIXTIME(o.changed) AS changed FROM commerce_order__adjustments a JOIN commerce_order o ON o.order_id = a.entity_id WHERE a.adjustments_value LIKE '%sourceId";s:6:"custom"%' UNION ALL SELECT 'order_item', oi.order_id, o.uid, o.ip_address, o.state, FROM_UNIXTIME(oi.changed) FROM commerce_order_item__adjustments a JOIN commerce_order_item oi ON oi.order_item_id = a.entity_id JOIN commerce_order o ON o.order_id = oi.order_id WHERE a.adjustments_value LIKE '%sourceId";s:6:"custom"%' ORDER BY changed DESC;
We’d like to thank the Drupal Security Team for helping us work through these issues. You can learn more about their work in this recent post by Tiffany Farriss, Interim CEO of the Drupal Association. We’d also like to thank Marcus Johansson for volunteering his own time to assist in identifying and reviewing these issues.If you maintain a Drupal Commerce site and are capable of executing SQL queries against your site’s database, you can use the following queries to help identify exploits based on the rubric outlined above.
Query 4: manually added adjustments
Query 1 would not catch a price lowered to The best confirmation is to match an order’s IP address and time against access log messages to ensure an order was placed through normal paths beginning with /checkout/#.





