Lion Cipher Lab
Exploit and fix clickjacking in your lab
- AppSec / DevSecOps
- Intermediate
- ~50 min
- Free · no account required
Why it matters
Clickjacking (also called UI redress) is a deceptively simple attack that keeps working against real sites: an attacker loads a target page in an invisible iframe, stacks it on top of a decoy the user WANTS to click ('Play video', 'Claim prize', 'Accept cookies'), and lines up the target's dangerous button — Delete account, Transfer money, Grant this app access — exactly under the decoy. The victim, already logged in to the target in another tab, clicks what looks like the decoy and actually fires the hidden action on their own authenticated session. Nothing looks wrong; there is no phishing…
What you'll learn
- explaining clickjacking (UI redress): tricking a user into clicking a control on a hidden, framed target site by overlaying it on a decoy the user thinks they are clicking
- defining an ORIGIN precisely — scheme + host + port — and seeing that localhost:5000 (victim) and localhost:8000 (attacker) are DIFFERENT origins, i.e. a cross-origin frame
- building a tiny vulnerable Flask app on your own machine with a state-changing 'Delete my account' button and NO framing protection, so any page can embed it in an iframe
- building a malicious attacker page that loads the victim in a transparent iframe and uses CSS position/opacity/z-index to stack the hidden victim button exactly over a visible decoy button
- running the attack in a real browser and watching a click on the decoy actually fire the hidden victim's Delete action — the click gets 'redressed'
- fixing it the modern way with the response header Content-Security-Policy: frame-ancestors 'none', plus the legacy X-Frame-Options: DENY for old browsers, set on the FRAMED page's response
- reloading the attacker page and reading the browser's console error ('Refused to display ... because an ancestor violates ... frame-ancestors') that proves the frame was blocked
- knowing the judgment: frame-ancestors supersedes the deprecated X-Frame-Options; 'none' vs 'self' vs an exact allowlist; the header must be on the framed page; SameSite cookies reduce impact but are NOT the fix; and frame-busting JavaScript is the old, bypassable approach
Steps in this lab
- The vocabulary: framing, clickjacking, and the UI-redress family
- Set up: confirm Python, make a folder, install Flask
- Build the VULNERABLE victim app (a state-changing button, no framing protection)
- Build the attacker page — the transparent-iframe overlay
- Run the exploit and watch the click get redressed
- Fix it: frame-ancestors 'none' (and X-Frame-Options for old browsers)
- Reload the attack and watch the browser refuse the frame
- Judgment: 'none' vs 'self' vs an allowlist, and why the other 'fixes' aren't
- What you did — and the reflex to keep