Blocking Card Testing Attacks In Woocommerce

A number of my clients’ sites that run Woocommerce have recently come under attack from “Card Testing Attacks” and so far Woocommerce have not been able to offer any practical advice that will stop these attacks other than “buy our security plugin!”

What is a Card Testing Attack?

Card Testing Attacks are attempts by bots (usually) to test if credit cards details are valid and usually take the form of an attempted purchase on a Woocommerce site of a single low value item. My clients’s sites were being hit with hundreds of these attacks per hour (600+) as the bots try to discover which of the thousands of nefariously obtained credit card details they have on file could be used.

Why Is This An Issue?

In the event of a card testing attack succeeding and the purchase being successfully placed then the retailer should refund the order and cancel it. Unfortunately this is not without cost to the retailer (my clients in this case) as the retailer will incur a card processing fee for the order and if the order is not refunded and the credit card owner disputes the transaction the retailer could be on the hook for a chargeback fee (usually about £15 per chargeback).

Tracing And Blocking Card Testing Attacks

Blocking these card testing attacks was harder than we (my clients and I) thought it would be. We tried blocking by IP address initially as all the original wave of attacks came from the same IP address. A few days later we blocked by domain / email address as a lot of the orders came from “@x.com” email address which were all placed using different IP addresses. That changed to “@gmail.com” email addresses which meant we couldn’t block that domain as thousands of legitimate customers use gmail addresses. All the gmail addresses were in the same format though, “[email protected]”, so something like “[email protected].” Blocking this email address format was a possibility but obviously the attackers would just move on to the next format and we’d be back to square one.

So I looked for similarities between the attacks and noticed the Woocommerce order attribution all marked these order attempts as “unknown.” Maybe I could block “unknown” orders. This lead me to this thread of the WordPress forums: https://wordpress.org/support/topic/attcked-by-card-testing-decline-orders-with-origin-unknown/ Someone else had the same thought as me but unfortunately Woocommerce support could offer no help other than “install ReCAPTCHA, try our paid-for security plugin”.

We tried adding removing guest checkout from the Woocommerce settings which had no effect, the attacks continued and now they had fake customer accounts too. We tried adding ReCAPTCHA to the checkout with no effect. This indicated to me that the attacks were not actually hitting the Woocommerce pages (account, card, checkout) and were probably being placed by a script of some sort. So I dug in to the log files and cross referenced the IP addresses against the access logs.

Below is a sample of the log files with the client’s domain obscured. I’ve left the IP address of this order intact, it resolves to a VPN company, Perfect Privacy. I’ve also left the User-Agent intact but this also changes with every order attempt. These are the only lines in the log for this sample order.

82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:51 +0000] "GET /wp-json/wc/store/products?stock_status=instock&order=asc&orderby=price&min_price=1&max_price=5000&type=simple&page=1&per_page=100 HTTP/1.1" 200 30258 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | - - 0.000 HIT 0 NC:000000 UP:-
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:52 +0000] "GET /wp-json/wc/store/cart HTTP/1.1" 200 447 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.246 0.246 0.246 BYPASS 0 NC:000100 UP:-
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:53 +0000] "POST /wp-json/wc/store/cart/add-item HTTP/1.1" 201 4663 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.273 0.273 0.274 - 0 NC:000100 UP:SKIP_CACHE_SET_COOKIEDT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:53 +0000] "POST /wp-json/wc/store/cart/update-customer HTTP/1.1" 200 1484 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.291 0.291 0.290 - 0 NC:300100 UP:SKIP_CACHE_SET_COOKIEDT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:54 +0000] "POST /wp-json/wc/store/cart/select-shipping-rate HTTP/1.1" 200 1484 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.285 0.285 0.285 - 0 NC:300100 UP:-DT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:55 +0000] "GET /checkout/ HTTP/1.1" 200 58156 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.697 0.702 0.702 BYPASS W NC:320000 UP:SKIP_CACHE_SET_COOKIE
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:56 +0000] "POST /?wc-ajax=ppc-data-client-id HTTP/1.1" 200 455 "https://www.domain.co.uk/checkout/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.606 0.606 0.606 - 0 NC:300000 UP:-DT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:37:57 +0000] "POST /?wc-ajax=ppc-create-order HTTP/1.1" 200 129 "https://www.domain.co.uk/checkout/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.678 0.678 0.678 - 0 NC:300000 UP:-DT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:38:00 +0000] "POST /?wc-ajax=ppc-approve-order HTTP/1.1" 200 16 "https://www.domain.co.uk/checkout/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 0.555 0.555 0.555 - 0 NC:300000 UP:-DT
82.199.130.34 www.domain.co.uk - [26/Nov/2024:21:38:04 +0000] "POST /wp-json/wc/store/v1/checkout HTTP/1.1" 400 1122 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36" | TLSv1.3 | 4.147 4.147 4.146 - 0 NC:300000 UP:SKIP_CACHE_SET_COOKIEDT

So what does the log file tell us? The first line shows us the attacker is using the Woocommerce WP REST API (/wp-json/wc/) to query the product catalogue and is ordering that query by price, getting the cheapest product. The next lines add the product to the cart, then sets the customer details and the shipping rate before proceeding to the checkout. Then the payment is processed by PayPal (in this case) which is not shown in the logs. The payment is rejected and the “customer” is returned to the checkout page. You can also see the “-” entry in the logs that shows the request has no referrer, hence the “unknown” order attribution in Woocommerce.

This lead me to the idea that if I can block the WP REST API I can stop the attacks. The legacy Woocommerce API was already disabled in the Woocommerce settings and the new REST API states “Access is granted only to those with valid API keys.” However the WP REST API documentation states:

Because the WordPress REST API does not verify the Origin header of incoming requests, public REST API endpoints may therefore be accessed from any site.

This is an intentional design decision

The documentation also says not to disable the REST API as it will break your site.

Stopping Card Testing Attacks

I wrote a little function to place in your theme’s functions.php page.

/** disable wc_endpoint to stop carding attacks **/
function disable_wc_endpoint_v1() {
    $current_url = $_SERVER['REQUEST_URI'];
    if (strpos($current_url, '/wp-json/wc/store/v1/checkout') !== false) {
        wp_redirect(home_url('/404.php'));
        exit;
    }
}
add_action('rest_api_init', 'disable_wc_endpoint_v1');

The code checks if someone is using the API to access the checkout endpoint and redirects the request to a 404 page instead. No more fake orders! I’ve tested this code on a couple of sites now for a week and all fake orders have been blocked. Of course if your site needs to allow orders via the API then this function won’t help. If your log files show the attacks are not using the v1 endpoints use this code instead (or as well).

/** disable wc_endpoint to stop carding attacks **/
function disable_wc_endpoint() {
    $current_url = $_SERVER['REQUEST_URI'];
    if (strpos($current_url, '/wp-json/wc/store/checkout') !== false) {
        wp_redirect(home_url('/404.php'));
        exit;
    }
}
add_action('rest_api_init', 'disable_wc_endpoint');

Let us know if this code has helped your site or if you have a better solution! The WP REST API documentation does say:

if you wish to prevent your site from being accessed from unknown origins you may unhook the default rest_send_cors_headers function from the rest_pre_serve_request filter hook, then hook in your own function to that same filter to specify stricter CORS headers.

I will look in to these functions and see if a better solution can be found.

*** UPDATE – September 2025 ***

One method to reduce the amount of card testing attacks on your store is to use rate limiting. This is especially useful if your site is being bombarded with attacks or you cannot use the code above to disable the API endpoint.

In December 2024 WooCommerce confirmed that rate limiting for Store API endpoints was included in WooCommerce (8.9.0) as standard but was disabled by default. As of WooCommerce 9.6 (I think) users can enable rate limiting through the WooCommerce Settings area (WooCommerce > Settings > Features > Rate limit Checkout). Woo published their own blog article on ways to limit card testing attacks and also acknowledge that many CAPTCHA plugins do not protect the Checkout Block or Store API.

If you’ve found this post useful please consider using the “buy me a coffee” widget to show your appreciation.

One thought on “Blocking Card Testing Attacks In Woocommerce”

  1. I was under bot attack on my woocommerce site . I tried Capthca, then cloudfare, then OOPSpam, then 3D secure, then combinations of all of them, and then all of them at the same time. Nothing worked. I added these 2 code snipits and the bots stopped immediatley! Thank you Mike for being so smart and for sharing with others

Comments are closed.