The Invisible Tweet Button — JavaScript Bug Hunt
Modelled on the Twitter "Don't Click" clickjacking worm (February 2009): a page showed a single button labelled "Don't Click" over an invisible frame of…
- Language: JavaScript
- Layer: Frontend
- Difficulty: Easy
- Concepts: Security, Config
- Modelled on: Twitter · 2009
- Visible tests: API responses are untouched and login stays protected; the tweet page cannot be framed
- Reward: 50 XP for a complete fix
Briefing
Modelled on the Twitter "Don't Click" clickjacking worm (February 2009): a page showed a single button labelled "Don't Click" over an invisible frame of Twitter's own page. Signed-in visitors who clicked it actually clicked Twitter's update button, posting the same link from their own account and spreading it to their followers. Twitter responded by stopping its pages from being framed by other sites.
This project is a reconstruction. headers.js builds response headers, but it only adds frame protection to a hand-picked list of "sensitive" routes — and the page that posts a tweet is not on it.
Fix buildHeaders so no HTML page can be framed by another site.
Bug report
BUG-TW-0212 · Priority: Critical · Reported by: security
buildHeaders(path, contentType) returns a header object that always has Content-Type = contentType. For EVERY HTML response (contentType starting with "text/html"), on any path, known or not:
- X-Frame-Options: "DENY"
- Content-Security-Policy: the route's own policy (routes.ROUTES[path].csp) followed by "; frame-ancestors 'none'", or just "frame-ancestors 'none'" when the route has none
Non-HTML responses keep the route's CSP if any and get no X-Frame-Options.
Observed: /home — the page with the tweet box — can be framed by any site.
Logs
[edge] GET /home referer=http://evil.example/dontclick.html sec-fetch-dest=iframe 200
[statuses] burst of identical "Don't Click" updates from unrelated accountsThe code as shipped
src/web/headers.js (editable)
var routes = require("./routes");
var FRAME_SENSITIVE = ["/login", "/settings"];
exports.buildHeaders = function (path, contentType) {
var h = { "Content-Type": contentType };
var route = routes.ROUTES[path] || {};
if (route.csp) h["Content-Security-Policy"] = route.csp;
if (FRAME_SENSITIVE.indexOf(path) !== -1) h["X-Frame-Options"] = "DENY";
return h;
};
Read-only context: src/web/routes.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.