Security

The Checkout Bug That Turned Out to Be a Card Skimmer

 ·   ·  ~8 min read

An automated page scan passing every check it covers but unable to complete a purchase flow, beside two manual checkout sessions compared: in one the payment gateway frame is visible, in the other it is hidden and a second card form served from the store's own domain has taken its place
Why the finding needed manual testing: everything an automated scan covers is checked on the page and all of it passed. The injection only revealed itself by comparing two guest checkout sessions run under deliberately different conditions.
TL;DR - Key Takeaways
  • The brief was the vaguest in ecommerce: some customers cannot complete their orders, most can, nobody knows why. No errors, no pattern, no reproduction steps.
  • Two separate causes, one symptom. An ordinary usability defect that had been quietly killing orders for months, and a payment skimmer running on the checkout page and nowhere else.
  • An automated scan would not have caught the injection. Everything a scanner checks, it checks on the page. This only revealed itself in behaviour.
  • The method was a comparison, not a clever tool: load the real checkout as a guest shopper twice under deliberately different conditions, and see what differs.
  • The second finding needed no attacker at all. Press Place Order with an incomplete card form and nothing happens: no message, no request, no trace anywhere.
  • Checkout is where UX, payments, security and configuration overlap, and when any of them fails the customer-visible symptom is identical.

A retailer came to us with one of the vaguest briefs in ecommerce: some customers can't complete their orders, most can, nobody knows why. No error messages, no pattern anyone could see, no reproduction steps. Two days of testing the live public checkout turned up two separate causes. One was an ordinary usability defect that had been quietly killing orders for months. The other was a payment skimmer, injected into the store's own content management system, running in shoppers' browsers on the checkout page and nowhere else.

This is a write-up of how a symptom that reads like a conversion problem turned out to be a security incident, and what any store owner can check on their own checkout this afternoon.

A note on detail: the store is anonymised throughout as a mid-market retailer running Magento 2. No domain, no names, no payment gateway, no attacker infrastructure and no payload appear below, and the article does not explain how the injection was built. Everything here is written from the defender's side: detect, verify, prevent.


Why "some customers can't check out" is the hardest ticket in ecommerce

Intermittent checkout failures resist every normal diagnostic. Support can't reproduce them. Analytics shows drop-off but not cause. The customers who hit the problem mostly don't report it: there's no error message to screenshot, so they click the button a few times, decide the site is broken, and leave. The ones who complain loudly enough to open a ticket are a small fraction of the ones affected.

That's why the symptom gets misfiled. It arrives at the marketing team as a conversion problem, and it gets treated with conversion tooling: heatmaps, session recordings, A/B tests on the button. Those tools describe the behaviour accurately and explain nothing about the cause.


What an automated scan would have said

Worth stating plainly, because it's the part most store owners get wrong about tooling: an automated site scan would not have caught the injection.

Why each automated check passed
What a scanner checks Why the injection passed
Is the site served over HTTPS with a valid certificate? Yes. It was.
Are scripts loading from suspicious third-party domains? No. The malicious code was served from the store's own domain, inside the store's own HTML, because it had been written into the store's database.
Does the Content-Security-Policy flag a violation? No. The exfiltration channel used a browser capability that CSP does not govern at all.
Does the page contain known-bad code signatures? No. The payload was encoded, and key identifiers were split into fragments and reassembled at runtime specifically to defeat pattern matching.
Does the checkout flow work? A scanner can't answer this. It cannot add an item to a cart, fill in an address form, and press Place Order.

None of that is a knock on scanners. They're excellent at what they cover, page-level SEO, performance, headers, structure, and we run one ourselves. It's a statement about the shape of the coverage. Everything in the list above is checked on the page. The injection only revealed itself in behaviour, and only on one page, and only sometimes.


What manual testing actually did

The method that produced the finding is unglamorous: load the real checkout as a real guest shopper, twice, under two deliberately different conditions, and compare.

In the first session, we blocked the suspicious loader from reaching the network before the page loaded. That gave us a clean baseline, the checkout as it behaves with no interference. In the second, we let it run.

The comparison did the work. In the baseline session, the store's payment form was the gateway's own isolated frame, visible and sized normally. In the second session, that frame was still in the page but forcibly hidden, and a second card form, not part of the store's code, served from the store's own domain, had appeared in its place, showing the same fields.

That difference is the whole finding. A card form served from your own domain is readable by any script on your page. A card form inside the payment gateway's frame is not, and that isolation is the entire reason gateway-hosted card entry is considered safe in the first place. Substituting one for the other removes the protection while looking, to the shopper, completely normal.

It also explained the intermittency. The substitution only completed when the attacker's server answered within a short window. Corporate networks, VPNs, privacy extensions and hardened browsers all blocked that quietly, so those shoppers checked out normally. Everyone else got a payment form that couldn't submit, because the store's payment component was still pointed at the original frame the customer never typed into. Some customers can't place orders. Most can. Exactly as described in the brief.


The second finding, which had nothing to do with the attacker

The same testing turned up an entirely separate defect, and in day-to-day revenue terms it was probably the more expensive of the two.

Fill in every address field correctly, leave the card fields empty, click Place Order. What happens:

Observable response to pressing Place Order with an incomplete card form
Signal Result
Visible field errors0
On-screen messages0
Loading spinnerNever appears
Network requests0. No attempt is ever made
ButtonStill there, still enabled

Zero network requests is the measurement that matters. The click is abandoned before anything is submitted, and the customer is told nothing at all. From their side, the button is simply dead. There's no error to report, nothing for support to search for, and no trace in any error-monitoring tool, which is precisely why it had survived so long.

This one needed no attacker, no sophistication and no bad luck. It just needed nobody to have clicked Place Order with an incomplete form on the live site.


What you can check on your own checkout today

None of these require a specialist. All of them are worth doing on a live storefront rather than staging.

  • Press Place Order with a deliberately incomplete card form. You should get a visible, specific message within a second or two. Silence is a defect, regardless of cause.
  • Press it with an incomplete address form too. Errors should appear and the page should scroll to them. If your submit button sits in a sidebar and the errors render in another column, a customer may see nothing change at all.
  • Confirm your card fields live inside your gateway's frame. Your payment provider's documentation will tell you what to expect. Card inputs served directly from your own domain, when your integration says they shouldn't be, is a red flag worth escalating the same day.
  • Review every CMS block, widget and "miscellaneous HTML" field in your admin for content you don't recognise. Injections favour these because they're rendered into pages by your own templates and rarely reviewed.
  • Check your admin activity log for edits to those blocks: who changed what, and when.
  • Check whether your Content-Security-Policy is enforced or report-only. Report-only policies produce reassuring dashboards and block nothing.
  • Look at what your checkout page logs in the browser console. Broken address autocomplete, failed template loads and null API keys all show up here, and any of them can be costing you orders on their own.

Why this ended up in a conversion audit at all

The brief we were given was a revenue question. The answer turned out to be a security incident with regulatory obligations attached, sitting underneath an ordinary UX defect, both producing the same customer-visible symptom.

That's not unusual. Checkout is the one page where UX, payments, security and platform configuration all overlap, and the symptom they produce when any of them fails is identical: the order doesn't complete and nobody knows why. Which is the argument for having someone look at the actual flow, from the outside, as a customer, on a schedule, rather than only when something has already gone visibly wrong.

Get a second pair of eyes on your checkout

Most checkout problems aren't this dramatic. Most are a silent button, an unreadable error, a section in the wrong order: the kind of thing that never generates a support ticket and quietly shaves points off conversion for years. They have one thing in common with the dramatic ones. You find them by testing the real checkout as a real customer, not by scanning the page.

Book a Manual Checkout Audit →

A manual audit puts a human behind the flow: real guest checkout testing, payment behaviour, UX, and the configuration issues automation can't see. Not ready for that? Run the free automated site audit for the page-level picture of design, SEO, performance and security issues, with revenue impact estimated per finding. It covers a different shape of problem, as the table above makes clear, and it is a reasonable place to start.


Written by Checkout Experts

Checkout Experts is a checkout-only optimisation agency with 20 years of experience and 500+ projects across Shopify, WooCommerce, Magento, BigCommerce, and custom builds. Part of ZestIQ, est. 2006.

This account is published with the client's permission and anonymised at their request. All testing was carried out on the live public storefront as an ordinary guest shopper. No card number was entered, no order was placed, and no administrative or authenticated access was used. Store, domain, payment provider and technical indicators are withheld and remain in the client report. Last reviewed October 2026.

Common Questions

Checkout Security FAQ

What automation can and cannot see, and what to do if you find something. Want a human behind your flow? Book a manual checkout audit.

Not reliably. Modern injections are served from the store's own domain, encoded to defeat signature matching, and often triggered only under specific conditions. A scanner reads the page it's given; it doesn't complete a purchase flow or compare behaviour across sessions.
Because the failure is conditional. Injected code that depends on reaching an external server does nothing at all when that connection is blocked, whether by a corporate network, a VPN, a privacy extension or a hardened browser. Those shoppers check out normally, which makes the problem look random from the inside.
Partly, and only if it's enforced rather than report-only, and only for the channels it governs. A CSP that permits inline scripts leaves the door open for injected content arriving inside your own HTML. Some browser capabilities also fall outside CSP's scope entirely.
Treat it as a card-data compromise from the moment of discovery, not from the moment of clean-up. Contact your payment processor and acquiring bank, then remove the injection, then find and close the entry point, rotating admin credentials and API keys and auditing every other place content can be rendered into a page. Removing the code without closing the entry point invites immediate re-infection.
It should be. From the customer's side, a checkout that's been tampered with and a checkout that's badly built look identical: the order doesn't go through. Anything that stops a purchase completing belongs in the same review.
Get in Touch

Have Someone Test Your Real Checkout

A human behind the flow: real guest checkout testing, payment behaviour, UX, and the configuration issues automation cannot see.

Usually responds within 24 hours
I want to