The Payment Page Script Nobody Pinned — JavaScript Bug Hunt

Modelled on the British Airways data breach of 2018, attributed to the Magecart group.

  • Language: JavaScript
  • Layer: Frontend
  • Difficulty: Medium
  • Concepts: Security, Validation
  • Modelled on: British Airways · 2018
  • Visible tests: a script matching its pin loads; a tampered script is refused
  • Reward: 50 XP for a complete fix

Briefing

Modelled on the British Airways data breach of 2018, attributed to the Magecart group. Attackers modified a JavaScript file that BA's website loaded, so that card details typed into the payment page were also sent to a server the attackers controlled. The UK Information Commissioner's Office later fined BA £20 million over the breach.

This project is a reconstruction of one defence that stops a tampered script from running: Subresource Integrity. loader.js fetches a script for the payment page along with the integrity value it was pinned to, and the locked sri.js computes a script body's integrity value (a small stand-in for SHA-256, so the project has no dependencies). The loader notices a mismatch — and loads the script anyway.

Fix loadScript so only a script whose integrity matches its pin is ever added to the page.

Bug report

BUG-BA-SRI · Priority: Critical (card data) · Reported by: security review

loadScript(page, src, integrity, fetch) — page is { scripts: [], warnings: [] }; fetch(src) returns the script body. Returns { loaded, reason }:

  • integrity missing or empty -> { loaded: false, reason: "missing-integrity" }
  • sri.integrityOf(body) !== integrity -> { loaded: false, reason: "integrity-mismatch" }
  • otherwise push { src, body } onto page.scripts and return { loaded: true, reason: "" }

A refused script is NEVER added to page.scripts.

Observed: a modified vendor script produced a warning and was executed on the payment page.

Logs

[checkout] integrity mismatch for /cms/js/modernizr-2.6.2.min.js (expected sri-4be1…, got sri-9c07…)
[checkout] scripts loaded: 14 (0 refused)

The code as shipped

src/checkout/loader.js (editable)

var sri = require("./sri");

exports.loadScript = function (page, src, integrity, fetch) {
  var body = fetch(src);
  if (integrity && sri.integrityOf(body) !== integrity) {
    page.warnings.push("integrity mismatch for " + src);
  }
  page.scripts.push({ src: src, body: body });
  return { loaded: true, reason: "" };
};

Read-only context: src/checkout/sri.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.