DEV Community

Cover image for The Ghost Endpoint: How an Undocumented Magento 2 REST Route Bypasses reCAPTCHA
Freento
Freento

Posted on

The Ghost Endpoint: How an Undocumented Magento 2 REST Route Bypasses reCAPTCHA

Imagine waking up to a Slack alert showing 200+ failed payment transactions per hour on a production Magento store. Order increment IDs are jumping exponentially, customer names look like random strings, and credit cards are being declined in massive waves.
Classic card testing attack.
Your first instinct? Check IP logs, set up a web application firewall (WAF), or double-check your reCAPTCHA configuration on checkout. But what if the bots aren't even using your storefront checkout?
During a recent incident investigation for a US-based e-commerce store running Magento 2.4.7, we discovered a glaring loophole in the Magento 2 core: an undocumented, unauthenticated REST API endpoint that bypasses standard checkout pipelines and completely ignores Magento’s built-in reCAPTCHA.
Here is how the attack works under the hood and how we blocked it.
The Mystery: 200+ Declined Payments per Hour
In January 2026, one of our clients reported a sudden anomaly. Within a single hour, their payment gateway logged 211 declined transactions. Upon checking the database, order increment IDs had jumped by 215, but only 4 were legitimate customer orders.
The security team initially attempted standard incident response measures:
Analyzed access logs and identified suspicious IPs (mostly rotating VPNs and hosting providers).
Implemented temporary IP bans.
Added advanced logging to failed payment observers.
However, IP blocking quickly turned into a game of whack-a-mole. The bots rotated addresses across multiple regions instantly. We needed to understand how they were hitting the payment gateway without triggering the frontend security layers.
Deep Dive: Shifting Through the WebAPI Core
A combined analysis of web server logs and custom observers revealed something unexpected. The bots weren't scraping the frontend checkout page; they were talking directly to a "ghost" API route.
In Magento 2, there are natively two ways to place a guest order via REST API:

  1. POST /V1/guest-carts/:cartId/payment-information — This is the standard route used by the default Luma and PWA frontends. It processes payment data and places the order via GuestPaymentInformationManagement, enforcing the full validation pipeline.
  2. PUT /V1/guest-carts/:cartId/order — This route bypasses the frontend entirely. It triggers GuestCartManagementInterface::placeOrder to submit the order directly. As it turns out, this PUT endpoint is defined as fully anonymous in vendor/magento/module-quote/etc/webapi.xml: XML

Because ref="anonymous", anyone can call this endpoint without authentication. Since Magento’s built-in API rate limiting is disabled by default, bots have an open highway to execute automated brute-force card testing.
The Bot Automation Flow
The exploit is incredibly efficient for automated carding because it requires no browser emulation, no JavaScript execution, and zero account creation:

  1. POST /V1/guest-carts ➡️ Generate a guest cartId.
  2. POST /V1/guest-carts/{id}/items ➡️ Add a cheap SKU to the cart.
  3. POST /V1/guest-carts/{id}/shipping-information ➡️ Inject dummy address data.
  4. PUT /V1/guest-carts/{id}/order ➡️ Trigger the payment gateway.
  5. Evaluate payload response ➡️ Check if the credit card is valid based on the API response. Why reCAPTCHA Fails to Protect This Route Even if you have Google reCAPTCHA or Adobe's CAPTCHA modules turned on for your checkout pages, it will not protect you here. Magento maps its WebAPI reCAPTCHA verification inside ReCaptchaCheckout\Model\WebapiConfigProvider. If you inspect the source code, the validation rules are explicitly hardcoded to specific service classes: PHP public function getConfigFor(EndpointInterface $endpoint): ?ValidationConfigInterface { if ($endpoint->getServiceMethod() === 'savePaymentInformationAndPlaceOrder' || $endpoint->getServiceClass() === 'Magento\QuoteGraphQl\Model\Resolver\SetPaymentAndPlaceOrder' || $endpoint->getServiceClass() === 'Magento\QuoteGraphQl\Model\Resolver\PlaceOrder' ) { if ($this->isEnabled->isCaptchaEnabledFor(self::CAPTCHA_ID)) { return $this->configResolver->get(self::CAPTCHA_ID); } } return null; }

Notice what's missing? Magento checks for savePaymentInformationAndPlaceOrder (the POST route) and basic GraphQL resolvers. But it completely ignores GuestCartManagementInterface::placeOrder (the PUT route).
The bots don't have to break your CAPTCHA; Magento simply never challenges them on this endpoint.
The Solution: Intercepting at the WebAPI Layer
Since standard headless configurations and monolith setups virtually never use the PUT /V1/guest-carts/:cartId/order endpoint for standard checkout flows, the safest approach is to intercept and kill this route entirely.
We engineered a lightweight open-source module: Freento_DisableCartsEndpoint.
Choosing the Right Extension Point
Initially, we considered writing a plugin around GuestCartManagementInterface::placeOrder(). However, code review revealed a critical dependency: that exact core method is internally evaluated by GuestPaymentInformationManagement (the legitimate POST route). Hard-blocking the service method would break standard guest checkouts.
Instead, we attached our plugin to the WebAPI request validation layer (RequestValidatorInterface). This isolates the block purely to the HTTP REST routing layer without messing with underlying e-commerce business logic.
PHP
<?php
declare(strict_types=1);

namespace Freento\DisableCartsEndpoint\Plugin;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Webapi\Exception as WebapiException;
use Magento\Framework\Webapi\Rest\Request;
use Magento\Framework\Webapi\Rest\RequestValidatorInterface;

class DisableGuestPlaceOrder
{
private const CONFIG_PATH_ENABLED = 'freento_disable_carts_endpoint/general/enabled';
private const ROUTE_PATTERN = '#/V1/guest-carts/[^/]+/order#';

private $scopeConfig;

public function __construct(ScopeConfigInterface $scopeConfig)
{
    $this->scopeConfig = $scopeConfig;
}

public function beforeValidate(
    RequestValidatorInterface $subject,
    Request $request
): void {
    if (!$this->scopeConfig->isSetFlag(self::CONFIG_PATH_ENABLED)) {
        return;
    }

    if ($request->getMethod() === 'PUT'
        && preg_match(self::ROUTE_PATTERN, $request->getPathInfo())
    ) {
        // Obfuscate by throwing a 404 instead of a 403 Forbidden
        throw new WebapiException(
            __('Request does not match any route.'),
            0,
            WebapiException::HTTP_NOT_FOUND
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

}

Architectural Decisions We Made:

  • HTTP 404 over 403: A 403 Forbidden implicitly tells a bot script that the endpoint exists but access is restricted. Returning a 404 Not Found completely obfuscates the API footprint, forcing bad actors to assume the route is invalid.
  • Configurable Toggle: We mapped the plugin state to a system configuration flag (Stores → Configuration → Freento → Disable Carts Endpoint). If a third-party legacy ERP or a highly customized headless frontend actually depends on this specific REST route, developers can toggle it off without modifying code. The Result Once deployed to production, the wave of "Payment Transaction Failed" server notifications dropped back to baseline almost instantly. Bot traffic targeting the cart architecture received generic 404 responses, and legitimate user checkouts continued completely unaffected. As of Magento 2.4.7, this endpoint remains open and unmonitored by default core security frameworks. If you manage an enterprise Magento instance, here is what we recommend doing today:
  • Audit Web Logs: Run a quick grep query on your server access logs for PUT /V1/guest-carts/. Any consistent traffic here is a red flag.
  • Enable Alerting: Ensure that "Payment Failed Emails" are configured under Stores → Configuration → Sales → Checkout. If a bot attack breaks through, you need to know immediately, not after your merchant account gets flagged.
  • Enforce Rate Limiting: If you cannot block endpoints due to vendor integrations, configure Magento's native WebAPI throttling configs to mitigate raw thread execution. How do you secure your WebAPI endpoints against card testing? Let us know in the comments below!

Top comments (0)