quarkslab / mattermost-plugin-e2ee

End-to-end encryption plugin for Mattermost
Apache License 2.0
65 stars 8 forks source link

[webapp] Ensure webapp integrity #6

Open aguinetqb opened 2 years ago

aguinetqb commented 2 years ago

Ticket to track ideas/advances on that known limitation.

aguinet commented 2 years ago

I've done a PoC here: https://github.com/aguinet/mattermost-webapp/commit/04e50efa292c1b13c233abbeb13049a9462b7327

The overall idea assumes that we have a Page Integrity-like plugin who verifies that, for a given domain, only signed/known pages are rendered in the browser (something Page Integrity doesn't do AFAIK, but should be doable). This allows for:

With this in mind, we do two things in the aforementioned commit to have a complete verification of the mattermost webapp:

This solution has interesting advantages:

The main drawbacks is that plugin developers that want to try their develop plugins would have troubles. This means two versions of the webapp should be built: one for developers, one for production (potentially a non acceptable burden).

There might be another solution that would make this drawback disappear: if the Mattermost webapp includes by default the Webpack SRI plugin (and nothing else), then a dedicated Mattermost webapp integrity browser plugin could do the plugin-specific integrity attribute injection.

jupenur commented 2 years ago

The overall idea assumes that we have a Page Integrity-like plugin who verifies that, for a given domain, only signed/known pages are rendered in the browser (something Page Integrity doesn't do AFAIK, but should be doable).

Page Integrity doesn't provide this, or anything else particularly useful really. It might be doable but you should be aware of the pitfalls, like service worker caches.

a "root of trust" of the web application

I suppose this assumes you can just sign the root.html document and have everything else fall back on that via SRI. That's not really the case though, because even though mattermost-server is mostly a SPA, there are other HTML pages that it needs to occasionally render, error pages for example.

not rendering pages that are unknown

Because of the above, this will break things in unexpected ways unless server development is carefully aligned with e2ee plugin development.

use the Webpack Sub Resource Integrity (SRI) plugin while building the MM webapp. This has the effect to use SRI on every javascript loaded by the app.

This only applies to root.html, not the other HTML pages rendered by the app. SRI for those would have to be handled separately.

the only remaining things that doesn't have SRI in this setup are the plugins' webapp. To achieve this, we precompute the hashes these plugins are supposed to have per version (in this repository: https://github.com/aguinet/mattermost-sri ) , and query it from the webapp to add the SRI attribute to the created script element (done while loading a MM plugin).

This is assuming plugins only have client-side code in the bundle declared in the manifest and nowhere else. That's not the case for a lot of plugins. For example, many plugins provide integrations with third-party applications and authorize the user via an OAuth flow. OAuth flows often use additional stand-alone HTML pages to avoid unloading the main Mattermost chat tab. And those HTML pages obviously cannot be secured using SRI; there would have to be a mechanism to handle them in the browser extension.

Some plugins also load JavaScript in the main app dynamically in a way that wouldn't be covered by SRI. For example the Jitsi plugin, one of the plugins you already included in the PoC repo, needs to load a Jitsi API from a separate file. And even though that could be covered with a custom SRI setup, there's also a "compatibility mode" where the API is actually loaded from the external Jitsi server.

it is completely transparent for both administrators and users

It's not super clear to me what you mean by "transparent" here. If it's an essential property, maybe you can elaborate a little?

The main drawbacks is that plugin developers that want to try their develop plugins would have troubles. This means two versions of the webapp should be built: one for developers, one for production (potentially a non acceptable burden).

This is a non-issue. Mattermost already has a developer mode where it relaxes some security guarantees similar to what's needed here. The existing developer mode could be easily extended to support this use-case.


The biggest open question here is the design and implementation of the integrity extension. There are a lot of potential pitfalls, and it's not obvious to me that a secure implementation is necessarily even possible. There are questions around of how signatures are calculated and distributed, how signing keys are distributed and verified to be trustworthy, how the blocking is done technically, who maintains and distributes the plugin etc. Page Integrity provides some ideas but nothing that could be used as-is since it's terribly insecure itself.

aguinet commented 2 years ago

Thanks for your answer! Comments below:

The overall idea assumes that we have a Page Integrity-like plugin who verifies that, for a given domain, only signed/known pages are rendered in the browser (something Page Integrity doesn't do AFAIK, but should be doable).

Page Integrity doesn't provide this, or anything else particularly useful really. It might be doable but you should be aware of the pitfalls, like service worker caches.

a "root of trust" of the web application

I suppose this assumes you can just sign the root.html document and have everything else fall back on that via SRI. That's not really the case though, because even though mattermost-server is mostly a SPA, there are other HTML pages that it needs to occasionally render, error pages for example.

We could make a less "strong" model, that would be: if the page isn't signed, it is rendered but with javascript disabled?

use the Webpack Sub Resource Integrity (SRI) plugin while building the MM webapp. This has the effect to use SRI on every javascript loaded by the app.

This only applies to root.html, not the other HTML pages rendered by the app. SRI for those would have to be handled separately.

True. If these pages doesn't really need javascript, maybe we could be fine (see above).

the only remaining things that doesn't have SRI in this setup are the plugins' webapp. To achieve this, we precompute the hashes these plugins are supposed to have per version (in this repository: https://github.com/aguinet/mattermost-sri ) , and query it from the webapp to add the SRI attribute to the created script element (done while loading a MM plugin).

This is assuming plugins only have client-side code in the bundle declared in the manifest and nowhere else. That's not the case for a lot of plugins. For example, many plugins provide integrations with third-party applications and authorize the user via an OAuth flow. OAuth flows often use additional stand-alone HTML pages to avoid unloading the main Mattermost chat tab. And those HTML pages obviously cannot be secured using SRI; there would have to be a mechanism to handle them in the browser extension.

Some plugins also load JavaScript in the main app dynamically in a way that wouldn't be covered by SRI. For example the Jitsi plugin, one of the plugins you already included in the PoC repo, needs to load a Jitsi API from a separate file. And even though that could be covered with a custom SRI setup, there's also a "compatibility mode" where the API is actually loaded from the external Jitsi server.

Indeed I assumed plugins were fairly self contained. What we could say here is that plugins could have, as a hint, something like an "sri" boolean in their manifest, stating that every loaded resources is properly validated thanks to sri. Then an administrator that want their users to be able to authenticate the mattermost webapp would only use these plugins.

Coupled with a browser plugin that would enforce only loading signed pages with SRI-enabled resources, it could do the job (at least in our case).

it is completely transparent for both administrators and users

It's not super clear to me what you mean by "transparent" here. If it's an essential property, maybe you can elaborate a little?

I meant the overhead for users is zero (they won't see the difference). Mattermost administrators would only have to take of loading "SRI-enabled" plugins.

The main drawbacks is that plugin developers that want to try their develop plugins would have troubles. This means two versions of the webapp should be built: one for developers, one for production (potentially a non acceptable burden).

This is a non-issue. Mattermost already has a developer mode where it relaxes some security guarantees similar to what's needed here. The existing developer mode could be easily extended to support this use-case.

Good to know, thanks!

The biggest open question here is the design and implementation of the integrity extension. There are a lot of potential pitfalls, and it's not obvious to me that a secure implementation is necessarily even possible. There are questions around of how signatures are calculated and distributed, how signing keys are distributed and verified to be trustworthy, how the blocking is done technically, who maintains and distributes the plugin etc. Page Integrity provides some ideas but nothing that could be used as-is since it's terribly insecure itself.

Mostly agree on this part. Using Github as a "root of trust" could be good enough for a lot of use cases, with the plugins gathering signatures from the various release pages (as I did in the PoC for plugins). That's one idea, there are tons of others :)

jupenur commented 2 years ago

We could make a less "strong" model, that would be: if the page isn't signed, it is rendered but with javascript disabled?

There is an extension API to disable JavaScript (chrome.contentSettings) but it only works in Chrome and only allows scoping settings to full origins or wider, not individual pages.

aguinet commented 2 years ago

We could make a less "strong" model, that would be: if the page isn't signed, it is rendered but with javascript disabled?

There is an extension API to disable JavaScript (chrome.contentSettings) but it only works in Chrome and only allows scoping settings to full origins or wider, not individual pages.

The NoScript Firefox plugin manages to do this on a per (sub)domain basis, so I'd say that should be doable?