The download() method in catalog/controller/account/order.php has no authentication gate, unlike every other order action ( index() , picklist() , info() ). It passes the raw order_id GET parameter directly to the model, which filters by WHERE order_id=(int)X AND customer_id=(int)$this->customer->getId() . For unauthenticated callers, getId() returns 0 (hardcoded in system/library/customer.php:300–302 ), which is identical to the customer_id value stored for guest checkouts. Because guest orders are legitimately stored with customer_id=0 , the model filter matches any guest order with no account, no session, no credentials required. Enumeration is trivial due to order IDs being sequential integers.
Any unauthenticated attacker can iterate order_id values and retrieve the full invoice for every guest order ever placed, which includes the customer's full name, billing and shipping addresses, phone number, email address, product line items, and order totals. This constitutes unauthenticated disclosure of personal data for all guests who have ever checked out.
The download() method in catalog/controller/account/order.php has no authentication gate:
As compared to index() , picklist() , info() which all open with:
On a local test instance, insert test victim guest order.
Confirm the MySQL database has been populated correctly.
Download the invoice without any login or cookies.
Similarly opening the link directly in a web browser proves it.
Perform a control test against gated endpoint to prove requirement for authentication.
Viewing the gated endpoint link in the web browser redirects us to the login page.
Add the same gate used by info() and picklist() at the top of download() :
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
