How Pirate World Cup Streams Actually Work

I wanted to figure out how that trick hiding in plain sight actually worked. Pirate streaming sites have been around forever, but during the World Cup you couldn’t open a sports forum without someone dropping a link to a live match — full HD, no buffering, no login. I’d always assumed there was something exotic going on under the hood. A friend sent me a link before the England-Croatia match. “Watch this,” he said. So I opened DevTools.
I expected a scraped HLS playlist or a re-streamed feed. Instead I found a <video> tag, a Shaka Player instance pointing at a legitimate Akamai CDN, and two hex strings in a <script> block that were doing all the work. Those hex strings were the DRM decryption keys. In the page source. Not behind an API. Not encrypted. Just wrapped in JavaScript that looked scary and did nothing.
Then someone showed me an Android app doing the same thing for every World Cup match — but “properly,” with encrypted configs, Firebase Remote Config, a license server, the works. A dedicated “MUNDIAL FIFA 2026” section with 28 channels including multicam, player cam, and manager cam for every game. It took about an hour to realize it was the same vulnerability wearing a nicer suit.
What started as thirty minutes of curiosity turned into a weekend. By the end I had mapped 670 live streams across 169 CDNs in 33 countries from the web side, and 525 more channels from a single Android app — all protected by a DRM scheme the W3C itself calls a “testing” tool. During a World Cup where England is putting four past Croatia and every goal is simultaneously available on hundreds of unauthorized streams.
1 The Padlock With the Combination on the Back
Every browser that plays encrypted video uses the W3C’s Encrypted Media Extensions (EME). EME supports four key systems. Three of them — Widevine, FairPlay, PlayReady — use license servers, encrypted key exchanges, and hardware-backed decryption. The fourth is ClearKey.
ClearKey sends the decryption key to the browser in plaintext.
That’s it. Same AES-128 encryption as Widevine. Same MPEG-DASH delivery. But where Widevine negotiates keys through a secure channel and decrypts video inside a hardware sandbox the browser can’t peek into, ClearKey hands the raw key to JavaScript and says “here you go.”
WIDEVINE L1 CLEARKEY
─────────────────────── ───────────────────────
License server (HTTPS) No license server
Encrypted key exchange Key in plaintext JS
Hardware TEE decryption Software decryption
Key never in JS memory Key IS the JS
Per-device, per-session policy No policy at allThe DASH Industry Forum says ClearKey is “recommended only for testing purposes.” Some platforms decided to use it in production anyway. Both the websites and the app I analyzed depend on this decision.
2 Part 1: The Websites
I examined dozens of pirate streaming sites. They all follow the same pattern.
2.1 The Architecture
┌──────────────────────────────────────────────────────────────┐
│ HOW IT ACTUALLY WORKS │
└──────────────────────────────────────────────────────────────┘
STEP 1 STEP 2 STEP 3
User visits Clicks a channel Page loads an <iframe>
pirate-site.co (e.g. "Sky Sports") from a different domain
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ │ │ ┌────┐┌────┐│ │ player-host.co │
│ Channel │ click │ │ESPN││DAZN││ iframe │ │
│ Grid Page │ ───────> │ ├────┤├────┤│ ───────> │ Shaka Player │
│ │ │ │Sky ││beIN││ │ + DRM keys │
│ (static │ │ └────┘└────┘│ │ + manifest URL │
│ HTML) │ └──────────────┘ └────────┬─────────┘
└──────────────┘ │
│ fetches
▼
┌──────────────────────┐
│ LEGITIMATE CDN │
│ (akamaized.net, │
│ skycdp.com, │
│ indazn.com) │
│ │
│ Encrypted DASH │
│ segments │
└──────────────────────┘
The pirate site serves ZERO video.
The CDN bill belongs to the legitimate provider.
The pirate site hosts ~50KB of HTML.The pirate site is just a middleman that knows two hex strings. It points the viewer’s browser at a real CDN, hands it the decryption key, and the browser does the rest. The CDN never knows the viewer isn’t a paying subscriber.
2.2 The Key Is in the Source
The player host page contains a <script> block with the keys, wrapped in obfuscation that a 30-line Node script undoes in under a second:
// Before: 40 lines of _0x4a2f, IIFEs, string-array lookups
// After: this is what it actually does
var drmKeyId = 'a1b2c3d4e5f6a7b80000000000000000';
var drmKey = '0123456789abcdef0123456789abcdef';
player.configure({
drm: { clearKeys: { [drmKeyId]: drmKey } }
});
player.load('https://cdn.legitimate-provider.com/manifest.mpd');Two variables. Two hex strings. That is the entire “DRM protection.” The obfuscation — variable renaming, string arrays, control-flow flattening — is undone by eval(), because the browser has to run it too. If the browser can execute it, a script can execute it.
Once extracted, keys spread through M3U playlists on GitHub and Telegram. I found a single M3U file with 545 ClearKey entries across 92 CDNs. Public, searchable, indexed by Google. Time from extraction to global distribution: minutes. Time for the key to be revoked: usually never.
3 Part 2: The Android App
Not every pirate operation is a static webpage. Some ship a full Android app — with login screens, Firebase analytics, encrypted configs, and a professional UI. One app I analyzed during the World Cup takes this approach. 525 channels. 36 categories. A dedicated “MUNDIAL FIFA 2026” section with multicam angles for every match.

3.1 The Encryption (AES-128-ECB)
The app fetches its channel list as an AES-encrypted JSON from a remote server. Every field — stream URLs, DRM license URIs, HTTP headers — is base64-encoded AES ciphertext. The decryption key is a 16-character string stored in Firebase Remote Config, fetched at launch.
Sounds serious. Then you look at the ciphertext:
236 URLs share the same first 35 encrypted blocks.
Block 0: f91b7f273abccc6e... ← identical across all 236
Block 1: 932b23560f1d43ba... ← identical
Block 2: a7d788a399cea962... ← identical
...
ECB mode. Same plaintext block = same ciphertext block.
The URLs all start with the same CDN domain prefix.AES-ECB is the textbook example of how not to use AES. Every cryptography course teaches this with the ECB penguin — encrypt a bitmap with ECB and the image is still recognizable because identical plaintext blocks produce identical ciphertext blocks. This app encrypts 236 URLs that start with the same CDN domain, producing 236 ciphertexts with an identical 560-byte prefix. Pattern analysis alone reveals the structure before you even find the key.
3.2 The “License Server” That Isn’t
Once decrypted, the ClearKey credentials aren’t in a separate key exchange. They’re in the license URI itself, as plaintext query parameters:
drm_license_uri (after decryption):
https://[redacted]/?keyid=49eb924b...&key=6e131b04...
^^^^^^^^ ^^^^^^^^
ClearKey ID ClearKey Key
(in the URL) (in the URL)The app’s “license server” is a URL that contains the key. There is no challenge-response. No session binding. The app fetches the URL, the URL is the key, and the key decrypts the stream.
Three layers of indirection — Firebase Remote Config, AES encryption, a “license server” endpoint — all collapsing into the same failure: the decryption key ends up in the client, in plaintext, because ClearKey requires it to.
3.3 The App’s Full Stack
┌──────────────────────────────────────────────────────────────┐
│ ANDROID APP ARCHITECTURE │
└──────────────────────────────────────────────────────────────┘
1. App launches
└──> Firebase Remote Config
└──> Fetches AES key ("claveapp") + config URLs
2. App fetches encrypted JSON (525 channels)
└──> Base64 decode ──> AES-128-ECB decrypt
└──> Stream URLs, DRM license URIs, headers (plaintext)
3. For CLEARKEY channels (348 of 525):
└──> "License URI" = https://[redacted]/?keyid=XXX&key=YYY
└──> Key ID and Key are IN THE URL
4. App configures ExoPlayer with ClearKey
└──> Fetches DASH manifest from legitimate CDN
└──> Decrypts video with the key from step 3
Package name: com.example.myapplication
Encryption: AES/ECB/PKCS5Padding (textbook insecure)
Key storage: Firebase Remote Config (single API call to extract)
Key length: 16 characters, static, never rotatedThe package name com.example.myapplication tells you everything about the development rigor behind this operation. The default Android Studio template. Not even renamed.
3.4 The Live Channel List Lives in a Public Repo
The most quietly damning part — and the part that shows the real sophistication of the operation, which is mostly logistics — is where the encrypted config is hosted. The app fetches its channel JSON from a public GitHub repository. Anyone can browse it. Anyone can git clone it. Anyone can watch the commit history.
And the commit history is the interesting bit:

A commit lands every 4–5 minutes. The diff is always against tv (10).json or bearer.json — the encrypted channel list, and the auth credentials used to fetch new streams from upstream. The commit messages are all "Actualizar tv (10).json desde tv.json" or "Actualizar bearer.json desde panel - <timestamp>". Translation: an automated job, somewhere, is rewriting the channel config every few minutes and pushing the update so phones running the app pick up new stream URLs and rotated DRM credentials within one polling interval.
This is the actual moat of the operation. Not the AES-ECB (broken). Not the Firebase Remote Config (one API call). Not the “license server” (a URL with the key in it). The moat is operational: a piece of automation that keeps the channel JSON aligned with whichever upstream feeds are alive at any given minute, hosted in plain sight on a free CDN that they did not have to build. When a legitimate provider rotates a key, the bot notices, fetches the new one, re-encrypts the config, and commits. When a CDN blocks the source IP, the bot finds another one. The customers’ phones poll, see the new commit, fetch the new config, and continue watching the match. The user does not even see the rotation happen.
The defence everyone reaches for is “revoke the key.” But you cannot revoke a key faster than a script can push a new one to a GitHub repo. The mismatch in cadence is the whole game.
4 The Numbers
| Web audit | Android app | |
|---|---|---|
| Streams | 670 | 525 (348 ClearKey) |
| CDNs | 169 | 34 |
| Countries | 33 | ~8 (LATAM-focused) |
| Key storage | JavaScript <script> block | Firebase Remote Config |
| Obfuscation | JS variable renaming + IIFEs | AES-128-ECB (broken) |
| Key in plaintext at client | Yes | Yes |
| World Cup coverage | Dozens of sports channels | 28 dedicated channels + multicam |
Both approaches — the static website and the full Android app — end at the same place: a ClearKey key pair in the client’s memory, with no mechanism to prevent extraction, no session binding, no revocation, and no key rotation.
5 The Money, and Why It Won’t Stop
The economics explain everything. Pirate sites host ~50KB of static HTML and point your browser at someone else’s CDN. The app is a thin ExoPlayer wrapper around someone else’s infrastructure. Neither serves video. Both monetize through ad networks.
StreamEast served 1.6 billion visits before being raided in September 2025. The IPTV ring in “Operation Takendown” had 22 million users and EUR 250 million per month. A mid-tier site during the World Cup — a million visitors on match day, six ad impressions each, $1.50 CPM — clears $9,000 a day against hosting costs of essentially zero.
Enforcement keeps escalating — prison sentences in Spain, $18.75 million judgments in Texas, 27,000 feeds taken down the week before the World Cup — and new sites keep appearing. Because the vulnerability is architectural. You cannot arrest your way out of a spec that puts the decryption key in the browser. You cannot encrypt your way out of it either, as the Android app demonstrates: three layers of encryption, and the key still ends up in plaintext on the client, because ClearKey demands it.
The fix is migrating off ClearKey entirely — to Widevine, FairPlay, or PlayReady. Until then, the combination is written on the back of the padlock, and the World Cup is making sure everyone knows where to look.