Why this matters for PCI DSS
A clickjacking attack works like this: the attacker loads your real, legitimate page in an invisible iframe at z-index 1. They overlay decoy content (often "Click here to claim your prize") on top. The user clicks the prize button, but the click actually lands on the "Confirm payment" or "Delete account" button in the framed real page. Because the user is logged in, the action succeeds.
For PCI scope: clickjacking on a payment confirmation page can authorize unintended charges. PCI DSS 4.0 Requirement 6.2.4 requires defenses against "common software attacks" — clickjacking is on every standard list (OWASP, SANS).
How to fix it
Pick one approach and apply it to every response:
nginx:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Content-Security-Policy "frame-ancestors 'self'" always;
Apache:
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"
IIS:
<httpProtocol><customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
<add name="Content-Security-Policy" value="frame-ancestors 'self'" />
</customHeaders></httpProtocol>
Express (Node.js):
const helmet = require('helmet');
app.use(helmet.frameguard({ action: 'sameorigin' }));
app.use(helmet.contentSecurityPolicy({
directives: { 'frame-ancestors': ["'self'"] }
}));
WordPress — most security plugins (Wordfence, iThemes Security) have a one-click toggle.
Note on payment iframes: if your payment page is itself a Stripe / Adyen / Braintree iframe, the customer's browser is loading that iframe from the gateway's origin — clickjacking protection there is the gateway's responsibility, and they handle it. Your responsibility is the page that hosts the iframe.
Verify with: curl -I https://example.com/ | grep -i -E "x-frame|frame-ancestors"