What a CSP report would have shown during the British Airways breach
For 15 days in 2018, 22 lines added to a script on ba.com copied card details to baways.com. The connect-src report that names a host like that on its first request, and what CSP leaves unsolved.
Michael Cameron5 min read
On 5 September 2018, a third party told British Airways that data was leaving britishairways.com for a domain called baways.com. It had been leaving for 15 days. Every customer who paid on the website in that window had a copy of their card details sent to a server the airline had never heard of. The booking went through normally, so nothing on the inside looked wrong.
The request to baways.com is the part of this incident a browser could have reported. What the report would not have shown matters as much as what it would.
What happened
The ICO’s penalty notice sets out the sequence. An attacker used compromised credentials for a remote access gateway to reach BA’s internal network in June 2018, moved through it, and eventually edited a JavaScript file on www.britishairways.com. The edit copied payment card data to www.BAways.com, a domain the attacker owned, and stayed live from 21 August to 5 September 2018. BA’s own estimate was that 429,612 people were affected, most of them customers whose name, address, card number and CVV were taken. The ICO fined BA £20 million in October 2020, down from the £183 million it had first proposed.
BA announced the breach on 6 September and gave the window precisely: card payments made between 22:58 BST on 21 August and 21:45 BST on 5 September. Its early figure, as told to the BBC, was around 380,000 customers.
How the skimmer worked
Five days after the announcement, RiskIQ published its analysis of the modified file. RiskIQ crawled the site regularly and kept copies of its scripts, so it could diff them. The change was to BA’s own copy of the Modernizr library, version 2.6.2: twenty-two lines appended to the end of the file, a placement RiskIQ noted attackers favour because it leaves the library working.
The added code waited for the page to finish loading, then bound handlers to
the mouseup and touchend events on the payment form’s submit button. When a
customer pressed the button, the code serialised the payment form and the
field holding the payer’s name, and sent the result as JSON to baways.com. The
domain sat on a VPS in Romania and carried a paid certificate from Comodo
rather than a free one, which RiskIQ read as an effort to look legitimate.
The server’s Last-Modified header dated the clean file to December 2012 and
the modified one to the start of the period BA later disclosed. The mobile app
was affected too, because much of it loaded pages from the website, Modernizr
included.
The CSP report this would have produced
The file was on BA’s own origin, so loading it was unremarkable to a browser.
The request that followed was not. A payment page carrying a report-only policy
whose connect-src named only the hosts the page talks to would have sent this
the first time the skimmer fired:
{
"csp-report": {
"document-uri": "https://www.britishairways.com/travel/book/payment",
"violated-directive": "connect-src",
"effective-directive": "connect-src",
"original-policy": "default-src 'self'; connect-src 'self' https://api.example-psp.com; report-uri https://k3x9mp2nq7ab.ingest.us.headerhawk.com/r",
"disposition": "report",
"blocked-uri": "https://baways.com",
"source-file": "https://www.britishairways.com/cms/global/scripts/lib/modernizr-2.6.2.min.js",
"line-number": 21,
"status-code": 200
}
}
The page path and the policy are illustrative. The rest is the shape the
CSP specification describes for an
XMLHttpRequest, fetch() or sendBeacon() call to a host the policy does
not list.
blocked-uri answers the question nobody at BA was asking: where does the
payment page send data? Browsers may shorten a cross-origin value to its origin,
so the path the skimmer posted to might not appear, but the host does.
source-file and line-number come from the script that made the request.
Here that is the modified Modernizr file, and the line number lands inside the
22 lines appended to its end. A minified library is one long line, so a line
number above 1 in a file that should have one shows something was added before
anyone opens the file.
disposition says report: the request went through. A report-only policy
observes. It does not intervene.
Where it surfaces
HeaderHawk groups reports by effective directive and blocked host, so every one
of these would land in a single issue titled connect-src blocking baways.com,
with its report count and first- and last-seen timestamps. First seen is
roughly when the code went live, the fact RiskIQ had to reconstruct from a
Last-Modified header three weeks later.
Opening the issue shows the individual reports, with the page each came from, the source file and line the browser attributed the request to, the policy in force at the time, and the browser and operating system. Every report would name the payment step, unlike a globally loaded SDK that violates everywhere.
Alerting is on browser agreement. Reports for an issue accumulate in 15-minute windows, and a window reported by two or more browser families, or carrying 20 or more reports from one, goes to Slack, email or a webhook, then repeats no more often than the rule’s window, which defaults to a day. A payment page on an airline’s website would clear the first window quickly.
Where the report stops
The report names the request, not the edit. script-src 'self' allowed the
modified Modernizr because it was 'self', and no policy can say that a file
changed. The ICO’s notice names file integrity monitoring as the control for
that on the server, observing that it does not stop an attacker changing code
but does let the organisation find out. Subresource integrity is its
counterpart in the browser, for scripts whose contents you control.
The signal is as sharp as the policy on the payment page. A skimmer posting to
an analytics host already on the allowlist, or to BA’s own origin, satisfies
connect-src and sends nothing. So the payment step deserves a narrower
connect-src than the rest of the site.
And the report follows the request. A report-only policy on that page would have shortened the distance between 21 August and 5 September, and the news would have come from BA’s own dashboard instead of a third party.
Sources
- ICO, Penalty Notice: British Airways plc, 16 October 2020. Paragraphs 1.2, 3.25, 3.26, 4.1 and 6.92 are the ones cited above.
- RiskIQ, Inside the Magecart Breach of British Airways, 11 September 2018, via the Internet Archive. RiskIQ’s site no longer serves the post.
- BBC News, British Airways breach: How did hackers get in?, 7 September 2018.
- W3C, Content Security Policy Level 3, for the report format.