Skip to content

Why a script inventory misses the swapped QR code

Laís Peixoto

By Laís Peixoto

Growth Marketer, Proteside

Published · 6 min read

A script inventory answers "what runs on the page", and a QR swap needs no new script: it comes from an extension, an already authorized plugin or altered content. Catching it means comparing what the screen shows with what the store issued, in the payer's browser.

A sporting goods store in Curitiba, Brazil, passed its PCI DSS assessment in August. The script inventory for the payment page was complete: every file listed, justified and integrity-checked. In September a customer paid R$ 412 by Pix (Brazil's instant payment system), the order sat at “awaiting payment” and the money never arrived. That week's report showed no new script, no unauthorized change, no alert. All green. The QR code had been swapped anyway.

What the inventory was built for

PCI DSS requirements 6.4.3 and 11.6.1 exist because of one specific problem: scripts loaded on the payment page that capture the credit card or debit card details the customer types. Requirement 6.4.3 asks that every script be authorized, justified and integrity-protected. Requirement 11.6.1 asks for a mechanism that detects unauthorized changes to security headers and page content, checked at least weekly.

Tools built for these requirements do what they promise: they list what the page loads, flag a new script and notice when a known file changes. The question they answer is “what is running on this page?”

A swapped QR code asks a different question: “does what the customer sees match what the store issued?” The list of scripts answers the first and stays silent on the second.

Three scenes

Scene 1: Pix

The store creates the charge with its payment provider and receives a dynamic QR code and a copy-and-paste code. In the customer's browser, an extension installed months ago (a “coupon finder” from an extension store) recognizes the Pix code pattern and replaces the key and the payee name, keeping the amount. The customer scans, sees R$ 412 and a reasonable company name in the banking app, and confirms.

What the inventory sees: the usual scripts, with the usual integrity signatures. The extension runs in the customer's browser, beyond any list the store maintains.

What the inventory does not see: that the copy-and-paste text and the QR image changed after the page loaded.

Scene 2: Boleto

A shipping plugin, authorized and justified in the inventory for a year, receives an update through the developer's compromised account. The new version does everything it did before, plus one more thing: when the boleto page appears (boleto is the Brazilian bank slip, paid by typing or scanning a numbered line), it rewrites the numbered line and the barcode with those of a slip issued to a different payee, same amount, same due date.

What the inventory sees: an authorized script with a new version, a routine event many teams approve without reading the code.

What the inventory does not see: that the numbered line on screen is no longer the one the store issued. To the tool, the numbered line is content, just another piece of page data, and data changes with every order.

Scene 3: UPI and PromptPay

A store in India shows a UPI QR code carrying the merchant's payment ID. A store in Thailand shows a PromptPay QR code with the merchant number. In both, the swap comes from the platform theme, bought from a marketplace and present in the script list since day one. A quiet snippet in the theme replaces the QR image with one generated on the spot, carrying the ID of a pass-through account.

What the inventory sees: the theme, authorized since installation, with no recent file change (the snippet was always there, dormant).

What the inventory does not see: two different QR codes for the same charge, one on the server and one on screen.

What the scenes share

In none of them is there a “new” script to detect. The attack uses something already authorized (a plugin, a theme), something the store never controlled (a customer's extension) or a change to content the tool treats as data rather than code.

And the result is a valid payment as far as the financial system is concerned. In September 2026, Procon-SP, the consumer protection agency of the state of São Paulo, issued an alert about this: Pix QR codes and copy-and-paste codes altered on legitimate stores' pages, correct amount, no visual sign on screen, days after Kaspersky described a campaign that reached at least 90 Brazilian online stores. The advice to consumers was to check the payee's name and tax ID in the banking app. Correct advice, and not enough: customers do not know the store's tax ID, and criminals pick plausible names.

What verification has to do

If the swap happens after the page loads, in the customer's browser, the check has to happen there too. And it has to compare two things: what the store issued and what the screen is showing.

That applies to every payment method with a visible destination:

  • Pix: the key, payee name and amount inside the QR code and the copy-and-paste code.
  • Boleto: the numbered line and the barcode.
  • UPI: the merchant's payment ID; PromptPay: the merchant number.
  • Stablecoin wallets: the destination address and the network.
  • Credit card and debit card: the form that receives the details and the destination they are sent to.

Without the “issued” side, verification can only say that something changed, with no way to tell whether the change is legitimate. Without the “displayed” side, it checks a server that was right all along.

How Proteside does it

The script inventory stays, because PCI DSS asks for it and because it solves the problem it was designed for. It is the first step. The others are:

  1. Record what was issued. When the store or the provider generates the charge, Proteside receives the QR code, key, numbered line or address and stores it tied to the order.
  2. Check in the payer's browser. A light script on the payment page reads what is on screen (the QR code, the copy-and-paste text, the numbered line, the address) and compares it with the record from step 1. The check runs on every screen, for every customer, on every order.
  3. Block before confirmation. If what appears does not match what was issued, the customer sees a warning in place of the QR code and the copy button does not hand over the swapped code. The payment does not go out.
  4. Log the attempt. Each mismatch becomes an event with date, order, payment method, expected value and found value. The security team uses it, and so does the PCI DSS assessor, next to the inventory.

In the weekly report, the inventory says “no unauthorized scripts”. The check says “three QR swap attempts blocked”. The first sentence was true at the Curitiba store. The second would have saved the R$ 412 sale.

To see what each kind of payment page security tool does and does not do, based on public, dated information, visit the comparison page.

Back to blog