Magento 2 Security Alert: Preventing Customer Address Hijacking via API

Urgent Security Patch: Safeguarding Customer Addresses in Magento 2

The security of customer data is paramount for any e-commerce platform. Recently, a critical vulnerability was identified and addressed in Magento 2, specifically within the CustomerRepository::save() method. This issue, tracked as GitHub Issue #41629, exposed a potential flaw where a malicious actor could hijack and overwrite another customer's address details through a crafted API request. For merchants and developers, understanding such vulnerabilities and their fixes is crucial for maintaining a secure and trustworthy online store.

The Vulnerability: How Customer Addresses Could Be Hijacked

The core of the problem lay in how Magento 2's CustomerRepository::save() method handled customer address data. When a logged-in customer (let's call them Customer B) made an API call to update their own profile (e.g., PUT /V1/customers/me), they could include an address ID that actually belonged to another customer (Customer A). The CustomerRepository::save() method would then iterate through the incoming addresses, set the customer_id to Customer B's ID, and pass these to AddressRepository::save().

Crucially, AddressRepository::save() would load the existing address row based on the supplied ID and proceed to overwrite it. At no point in this specific flow was there a check to confirm that the provided address ID truly belonged to Customer B. This oversight meant that Customer A's address would be moved into Customer B's account and could be modified. Any fields not explicitly provided in Customer B's payload (like company name or VAT ID) would be retained, effectively transferring sensitive data.

This vulnerability created a significant data integrity and security risk, allowing unauthorized manipulation of customer information, which could lead to fraudulent activities or privacy breaches.

Existing Protections vs. The Gap

It's important to note that Magento 2 already had several mechanisms in place to prevent similar issues in other contexts. For instance:

  • AccountManagement::createAccountWithPasswordHash() would clone ID-bearing addresses with setId(null) to prevent accidental overwrites during account creation.
  • validateDefaultAddress() specifically checked ownership for default billing and shipping addresses.
  • Front-end components like Address\FormPost and GraphQL address resolvers also included ownership checks.

However, the direct address list processing within CustomerRepository::save() bypassed these specific checks, creating the critical security gap that was exploited.

The Solution: Robust Ownership Validation

The fix, introduced through a related pull request, implemented a vital ownership check directly within the CustomerRepository. Now, before an address is saved, the system verifies that any provided address ID indeed belongs to the customer whose profile is being updated. If an address ID is found that does not belong to the current customer, the system throws a NoSuchEntityException, specifically indicating 'addressId' as the problematic field.

This exception triggers a rollback of the entire save operation, thanks to Magento's existing TransactionWrapper plugin. This ensures that no partial changes are committed and the integrity of customer data is preserved. The behavior is consistent with how the AddressRegistry handles unknown address IDs, providing a unified and secure error response.

The manual testing scenarios provided in the issue clearly demonstrated the effectiveness of the fix:

1. Create customer A with one address, and note its address id (e.g. 5).
2. Create customer B and get a customer token for B.
3. As B, call PUT /V1/customers/me with {"customer":{"email":"","firstname":"B","lastname":"B","website_id":1,"addresses":[{"id":5,"firstname":"x","lastname":"x","street":["x"],"city":"x","postcode":"10001","country_id":"US","region_id":43,"telephone":"1"}]}}.
4. Before the fix: the call succeeds, address 5 now has parent_id = B. After the fix: the call returns 404 "No such entity with addressId = 5" and address 5 is unchanged.
5. Updating B's own addresses by their ids, and adding new addresses without an id, still work as before.

Furthermore, a dedicated unit test, CustomerRepositoryTest::testSaveWithAddressOfAnotherCustomer, was developed to confirm the vulnerability's existence and validate the fix, ensuring that the issue does not resurface in future updates.

Implications for Magento Users and Developers

This fix underscores the continuous efforts by the Magento community and core team to enhance the platform's security. For Magento 2 merchants, it highlights the importance of keeping their installations updated to the latest versions to benefit from such critical security patches. For developers, it serves as a crucial reminder to implement robust ownership and authorization checks at every layer of data manipulation, especially when dealing with sensitive customer information and API endpoints.

Staying informed about such security updates is vital for maintaining a secure and compliant e-commerce environment, protecting both your business and your customers' trust.

Start with the tools

Explore migration tools

See options, compare methods, and pick the path that fits your store.

Explore migration tools