Anki Mediasrv Test

This is a test exploit to demonstrate the Anki mediasrv vulnerability.

When you start the Anki desktop application, it automatically starts a web service called the mediasrv api.

That can be safe, so long as the service implements strict Origin checks.

Unfortunately, As of version 25.09.2, Anki does not check Origin at all, so the service will exposed to any website you visit.

Luckily, recent versions of Chrome implement a new security feature called PNA, which protects against attacks like this. As far as I know, no other browsers currently implement it, so Firefox and Safari users are likely affected.

This exploit was tested against Anki 25.09.2, on Windows 11 x64 with Firefox 150.

It might work against Safari or other browsers, but will need some tweaks.

This exploit asks you to manually start each stage so you can see what it's doing, but a real attacker would run it automatically!

Please make sure Anki is running before testing!

This exploit probably doesn't work without prompts in recent Chrome because of PNA.

Please use the http version of this page, otherwise we need to open a window.

Click the "Scan" button to begin...

When a stage completes (✅), move on to the next one.

Stage 1: Find Ephemeral Port
Details...

Anki opens an ephemeral port on startup for the mediasrv API. That means the port is effectively random, and there is no way to know it. The only option is to just test all possible ports until we find it!

We request a resource we know Anki will serve, and see if it succeeds. If it does, then we know we've figured it out. Click Scan... to begin the search.

Note: This can take over a minute, maybe longer! A real attacker would do this silently in the background.

Lowering the timeout speeds up the scan significantly, but it might miss the port on a slower computer. If the scan doesn't find the Anki port, try increasing the timeout or decreasing the parallelization and try again.

Stage 2: Force File Download
Details...

We can trick Anki into reading any local file using a simple directory traversal bug. Anki does try to detect that for some handlers, but there are a lot of handlers and it missed quite a few, like this one.

Surprisingly, this isn't very useful! You can get Anki to read a file, but that doesn't mean someone else can read it!

A solution is to download our own file, then get Anki to host it through it's own webserver. The browser will believe Anki is requesting it, so will grant it additional permissions.

We can do this using the html5 download attribute, click Download... to demonstrate. An attacker does need you to interact with a page to do this, but any click on any element is enough.

Stage 3: Load Listener Frame
Details...

If that works, we now know the path of a file on the host filesystem. We can ask the browser to display it, and it will think Anki is requesting it.

Click Start... to try this. If you see it say "OK", then it worked.

The file we uploaded is a simple message listener, it will execute anything we send it via postMessage(), so we can make the browser think Anki is requesting it.

If you like, specify the number of path components to traverse to get to $HOME or %USERPROFILE%, and which path handler in Anki to target.

Safari

For some reason, Safari doesn't consider localhost a secure context, so this iframe would be considered mixed-content and be blocked from https. You can still window.open() it, postMessage() to window.opener, then window.close() though.
Please make sure you've loaded this page over http, not https for the most effective demo. If you don't, you can try the window.open() version, just be aware it doesn't look as effective! Either way, a real attack would not require prompts.

Stage 4: Request a File
Details...

Now we just have to tell it what file we want. The path should be relative to the %USERPROFILE% (where Anki is installed). This isn't a technical requirement, just makes it simpler to guess where files are.

Click Request... to ask the listener to fetch the file we want, then postMessage() it back to us so we can read it.

Stage 5: Exfiltrate Result
Details...

If that works, we now have full filesystem access and can read any files we want. The file we stole should be displayed here, which we can then POST anywhere we like.

This can be anything, documents, wallets, keys, etc. I have set a harmless default most users will have just for testing.

Click Proof... to demonstrate javascript on this page can read the file shown below, and it isn't just an iframe trick.