Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Bug reports and enhancement requests
Post Reply
tsutsu
Posts: 3
Joined: Thu Mar 21, 2024 4:28 pm

Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Post by tsutsu »

The Server-Side Rendering client helper libraries I'm talking about:
  • htmx
  • Phoenix LiveView
  • Hotwire
These small, self-contained Javascript libraries are delivered by Server-Side Rendered web frameworks, and exist for the purpose of avoiding a Javascript "fat client." They are not user-customizable, and have a very limited and — as far as I know — safe/secure/non-fingerprinting set of actions they can perform on the page.

Essentially, these Javascript libraries exist to allow two things:

1. They allow event handlers to be bound to HTML elements — but without the ability of the app to specify any client-side event handling code. Instead, the app specifies (through HTML attributes) the data that should be collected from the DOM; this is submitted by the framework to the server backend; and the backend then responds with partial HTML, which replaces an app-specified (again, through HTML attributes) section of the DOM.

2. They allow the the server backend to push updates to the page to the client, without any client-side interaction. This is accomplished by the client library holding open a websocket to the server, which can push partial HTML at the client library, which will then apply the patch directly to the page's DOM.

Other than this, they don't do anything else. As long as there is no other JS loaded into the page to interfere with their operation, then having such a JS library loaded into the page, doesn't enable the page to do anything else, either.

It is my belief — correct me if I'm wrong! — that as long as the partial HTML getting "patched in" by these JS helper libraries is itself filtered through NoScript (as it already would be, given how NoScript works) — then there should be no security or privacy implications to modifying NoScript to allow these libraries, and only these libraries, by default. Even when "scripts" are disabled.



Motivation (use-case 1: regular web-browsing)

I use NoScript because I want to protect myself from malicious websites, advertising and analytics and browser fingerprinting, etc. But I don't have any ethical objection to "rich web-apps."

In general, I feel that we could get good UX from the web, without having to sacrifice security or privacy/anonymity. It shouldn't have to be a choice.

Currently, this is mostly impossible, as our only options with NoScript are either to disable all scripts, or to micromanage the enablement of scripts on a per-URL basis.

But "rich web-apps" are usually built as fat monoliths, with the fingerprinting/advertising/analytics integrated right into the same scripts that make the web-app function. And even if you can find a web-app that keeps its analytics out of its core scripts, an attacker who gains control of that web-app's backend can still just replace the script that lives at the "core script" URL — the one you already whitelisted in NoScript — with a malicious script at the same URL. And NoScript wouldn't notice!

These SSR helper libraries offer an alternative: they provide a very restricted client-side integration, which does not change often. Sites that use these SSR frameworks + helper libraries, as long as they use no other JS, are restricted to doing only good things. If NoScript can verify that a website is using only these SSR helper libraries, then NoScript can enable rich UX without enabling fingerprinting/advertising/analytics or malicious code.

This would lead to a virtuous cycle: with more websites developed that work and provide good UX under NoScript, more users would feel NoScript is reasonable to use; which would in turn mean that more web developers would look at their access stats and consider supporting NoScript. Which in this case would no longer mean having to code an alternate, entirely JS-less website for just these users — but rather, switching from a "fat JS client" implementation, to an "SSR framework with a framework-provided thin client-helper JS library" implementation, which would serve both regular and NoScript users equally well!



Motivation (use-case 2: Tor Browser)

Tor Browser includes NoScript. Tor Hidden Services are coded under the expectation that users not only have JS disabled, but also that they are not willing to enable Javascript — as arbitrary server-provided JS has high potential for de-anonymization/fingerprinting attacks.

This severely limits the user experience of Tor Hidden Services vs regular web services. (Have you used a Tor hidden-service "chatroom"? They suck! The whole content-frame must refresh at some interval, losing your scroll position.)

Developers are less likely to think it worthwhile to "port" their web service to a Tor hidden-service exposure, as they believe that, to actually serve Tor Browser users effectively, their website needs to be usable without JS enabled — and for some websites, this is too much of a challenge.

And this in turn makes Tor less powerful as an anonymity tool, as with fewer truly-benign web services, there are fewer reasons for governments and corporations to not just block Tor altogether.

If Tor Browser configured NoScript to allow "well-known scripts" under the default Safest security level, then you could have rich web-app experiences on Tor hidden-services, without subjecting Tor Browser users to fingerprinting/de-anonymization attacks.



Proposed Implementation

Split the NoScript "script" filtering component out into two components: "well-known scripts" vs "arbitrary scripts".

NoScript can (but doesn't have to) allow "well-known scripts" in the default profile.

NoScript would recognize a script as a "well-known script" not by its URL, but by its Subresource Integrity content-hash. (This means that only <script> tags with the integrity attribute set would be candidates for being considered a "well-known script.")

NoScript would maintain a (burned-in per version) set of "well-known script" content-hashes. If NoScript recognizes a <script>'s integrity hash as being in this set, then the script is considered a "well-known script", and filtered according to the current setting for "well-known scripts" rather than the setting for "arbitrary scripts."

Upon the release of new versions of these SSR helper libraries, the library developers — motivated, hopefully, by the desire to support NoScript users — would submit the new version of their script to NoScript, for validation as a "well-known script." (Sort of like how new versions of App Store apps are submitted to the App Store owner for validation.)

For incremental releases of a script, the validation process could use an automatic static-verification pass, done against the delta between the current and previous versions of the script, to ensure that no new browser-API surface has been added to the library since the last release. If this check passes, then the new script version likely doesn't need manual human verification before adding its hash to the burned-in list. (Which means this whole flow could be done in a Github Action — with the devs of a "well-known script" opening a PR that adds the script's content-hash and a canonical URI for it to a .js file; the CI action "testing" by fetching the .js file from the canonical URI, verifying it against the hash and validating its API surface against the (also fetched) previous release; and then auto-merging the PR if these checks succeed.)

For brand new scripts (not that much "growth" would be expected — there's not much proliferation of these SSR framework client-libraries!); or if the automated verification fails — then it'd be up to a human on the NoScript team to look at the script and decide if it's safe. The human validator on the NoScript side would be free to be as paranoid and lazy as they want with these scripts; they could reject scripts that are not easily validated, thereby pushing most of the work of making such scripts easily validated, onto the authors of these scripts. (Given the worldviews of the authors of these SSR frameworks, I think they'd be perfectly okay with this.)



Alternatives

Rather than implementing this functionality directly into the NoScript browser extension, I see a few other ways of accomplishing the same goals:

1. Create a separate browser extension that does the above. This extension would expect NoScript to be running, and would look for scripts that weren't loaded because of NoScript policy, but which fit its definition of "well-known scripts." It would then load these scripts into the page context.

IMHO, this approach is just the same thing with extra steps, for a worse end-user experience — browser users would need to manage two separate sets of policy, in two separate extensions, rather than "well-known scripts" being just another checkbox within NoScript.

2. Create a separate browser extension which ships with known-good versions of these SSR client libraries embedded into it. This extension would look for a specific HTTP response header (or HTML <meta http-equiv> equivalent) specifying an SSR framework by name + major client-library ABI version; and would respond by injecting its embedded copy of the appropriate client library into the page. (This is essentially a way to pretend that these SSR frameworks are base browser functionality.)

This approach is less secure, in that there's no way for users to turn off this injection.

This approach would also require that the embedded versions of the client library are both backward- and forward- ABI-compatible with arbitrary server-side versions of the web frameworks of the same major version. (Which would be annoying to the devs, but good for stability of the ABI, and thereby number of eyes that see+audit each ABI version and its client-library impls.)

This approach would move control over implementations of the client libraries to the developers of the extension, and/or to any browser that implements the functionality of the extension "in core" (think: Brave Browser's implementation of ad-blocking.) This could be good for security — proliferation of implementations usually is — but would also mean that there could be malicious or simply "leaky" implementations of the extension functionality that web backends could discover and exploit.

Personally, I don't think either of these approaches are better than just having NoScript have "well-known scripts" as an exception. Which is why I'm proposing what I'm proposing. :)
Last edited by tsutsu on Thu Mar 21, 2024 6:39 pm, edited 1 time in total.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
barbaz
Senior Member
Posts: 10847
Joined: Sat Aug 03, 2013 5:45 pm

Re: Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Post by barbaz »

tsutsu wrote: Thu Mar 21, 2024 5:56 pm I use NoScript because I want to protect myself from malicious websites, advertising and analytics and browser fingerprinting, etc.
And the proposal you've outlined would harm NoScript's ability to help these purposes. The scripts would have to be downloaded to verify the hash, which right there opens another vector for tracking and possible CSRF, even if the script is subsequently rejected.

The only safe way to implement this feature would be for NoScript to bundle known-safe versions of these libraries, then somehow detect when a script-disabled webpage would use such library & if so load its own bundled copy into the page. This used to be possible with surrogate scripts, however surrogates are not coming back to NoScript - viewtopic.php?p=103883#p103883
So it seems unlikely this would get implemented, sorry.

You might consider looking into LibreJS, especially if the libraries you want to allow are Free Software.
*Always* check the changelogs BEFORE updating that important software!
Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
tsutsu
Posts: 3
Joined: Thu Mar 21, 2024 4:28 pm

Re: Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Post by tsutsu »

barbaz wrote: Thu Mar 21, 2024 6:34 pm possible CSRF
Fine, then make an additional restriction that for a script to be considered a candidate for being evaluated as a "well-known script", it needs to be same-origin to the site loading it.

The script being same-origin would normally obviate the need for a script integrity attribute. But, for the specific case of these SSR frameworks, they'd definitely be willing to add it anyway. It'd introduce a "useless" integrity check to non-NoScript users, but hashing a 2KB JS file is extremely cheap.

Mind you, if the browser does the download to enable integrity-attribute validation before it hooks URL-loading to give NoScript a chance to deny the attempt, then yes, you shouldn't use the integrity attribute.

Instead, at that point, just make up your own integrity-alike data attribute (data-noscript-integrity); and ask the SSR frameworks to emit that. (They'll still do it.)

Then do one of the following:

1. during script-load decision-making, after checking that the hash is on the whitelist and that the URI is same-origin, network-fetch the script, and do a (JS-implemented) cryptographic hash of it; or

2. during script-load decision-making, if it has the data-noscript-integrity attribute, and initial checks pass, then deny the request, put the (URI, hash) pair into a "pre-approved once for this page" cache, send it to the page's NoScript content-script as a pushMessage, and queue a deletion of the script element. Over in the page's NoScript content-script, on receipt of the pushMessage, take the (script URL, hash) pair, and use it to recreate the script tags in the DOM, this time with the browser-driven integrity attribute, so that the browser will do the fetch + integrity verification itself. And back in the extension, when the network request comes back around to the extension for decision-making, pre-approve it because it's in cache, deleting the cache-entry in the process.

(Nicely, given either of these approaches, there'll be no "useless" check in non-NoScript contexts — so the silly micro-optimizer objectionists won't be summoned.)

Neither of these approaches would work exactly as given under Manifestv3, mind you.

But the second approach in its more-general sense — "deny, but evaluate more-complex policy after+async to the denial, and if approved, figure out what it is you denied and inject a new script tag that approves it this time" — could work under Manifestv3, if you're careful. You just need to get all the info required to approve the second time around, into the URL. For example, by generating a pre-approval pre-shared-key token in the extension; adding it to each re-injected script URI as a fragment; and then telling the network-filter API, as the first-applied filter, that any URI with this pre-shared-key token in it should be allowed.
barbaz wrote: Thu Mar 21, 2024 6:34 pm vector for tracking
As long as any subresource-network-request-triggering capability is enabled for the site, that vector for tracking already exists. By default, NoScript grants the "frame" capability — which gives the page access to invisible <iframe>s, and so exposes exactly the same vulnerability surface a <script> hit would achieve.

And again, restricting "well-known script" loads to same-origin would mitigate this.

(Also, "well-known scripts" as a capability would probably be default-disabled for sites set to "untrusted" — just like every other capability is.)



Something you didn't mention, but you probably should have: the concept of "well-known scripts" isn't useful at all, unless the "fetch" capability is also granted for the site. Which it wouldn't be by default.

I think it'd be okay for there to be:

1. a new, separate "same-origin fetch" capability, where "fetch" would then become "arbitrary-origin fetch";

2. for the "same-origin fetch" capability to be allowed by default in any/all site modes where "well-known scripts" are enabled by default.

(On a per-tab basis, even in Manifestv3, evaluating just same-origin-ness of fetches, can be done with a single network-filter-API rule/pattern — "deny if fetch origin is not toplevel origin.")
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
barbaz
Senior Member
Posts: 10847
Joined: Sat Aug 03, 2013 5:45 pm

Re: Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Post by barbaz »

Took a quick look into htmx and Phoenix LiveView, and their events support seems enough to perform clickjacking attacks that would not be possible on truly scriptless pages. In NoScript's security model, this should not be allowed on every website by default, it would be appropriate to have to set websites using these technologies to Trusted (or a permissive Custom) if you trust the site & want the rich functionality.
tsutsu wrote: Thu Mar 21, 2024 6:56 pm Something you didn't mention, but you probably should have: the concept of "well-known scripts" isn't useful at all, unless the "fetch" capability is also granted for the site. Which it wouldn't be by default.
Actually, "fetch" capability is enabled by default in both Default and Trusted presets.
*Always* check the changelogs BEFORE updating that important software!
Mozilla/5.0 (X11; Linux x86_64; rv:124.0) Gecko/20100101 Firefox/124.0
tsutsu
Posts: 3
Joined: Thu Mar 21, 2024 4:28 pm

Re: Proposal to default-allow Server-Side Rendering frameworks' client helper libraries

Post by tsutsu »

barbaz wrote: Thu Mar 21, 2024 8:08 pm Took a quick look into htmx and Phoenix LiveView, and their events support seems enough to perform clickjacking attacks that would not be possible on truly scriptless pages.
Can you outline how this attack would work? I can't visualize a clickjacking attack that would be enabled by one of these frameworks but not by the default set of NoScript capabilities.

Keep in mind that NoScript already allows iframes and arbitrary CSS, with no mitigations against e.g. CSS animations, CSS 3D transforms, CSS opacity, etc. So you can just load up the Facebook login prompt or whatever in a low-opacity iframe, with initial rotateX(90deg) so its width is zero, and with pointer-events: none, so that it's unclickable; then have a CSS animation defined that causes the iframe to wait around for a couple of minutes, before CSS-3D-rotating into view (but not really "view", because it's still only 1% opacity), setting pointer-events: auto as the animation completes. That's all pure CSS, no JS.
barbaz wrote: Thu Mar 21, 2024 8:08 pm In NoScript's security model, this should not be allowed on every website by default, it would be appropriate to have to set websites using these technologies to Trusted (or a permissive Custom) if you trust the site & want the rich functionality.
My whole premise here, is that for ecosystem evolution and "herd immunity", defaults matter. If a separate NoScript "well-known scripts" capability existed, but wasn't enabled by default, then nobody would use it except when they'd also enable the "scripts" capability generally — making a separate capability pointless.

Ignoring the specific implementation for a moment, and even ignoring the specific client libraries I named, the spirit of what I'm trying to propose here is this:

1. I'd like to come up with a theoretical "safe client-side abstract machine" — an abstraction over the browser Javascript runtime, where if the browser is prevented from running arbitrary JS, but is allowed to embed an implementation of this abstract machine to interact with the page, then the page will gain some capabilities that a JS-less page does not have, but will also stay within the limits of what extensions like NoScript would be willing to support in their "Default" security modes. It is exactly the limits of NoScript's "Default" security mode, that would define what capabilities such a "safe client-side abstract machine" would enable.

2. Given such an abstract machine definition, I'd like to then find some existing client-side libraries that represent concrete implementations of this abstract machine. Either already obeying the constraints of the abstract machine "spec"; or, if not, able to be easily "ported" to this abstract machine, by stripping out some of their non-critical capabilities.

I already did step 1 in a very hand-wavy way, arriving at a mental image of a "safe client-side abstract machine", for which step 2 then spat out htmx et al as libraries that could very well fit the constraints; or if not, could be trimmed down a bit to fit the constraints.

Of course, it'd be better to actually do step 1 "for real" — to formally nail down exactly what capabilities would/wouldn't be considered safe for an htmx-like client helper library to offer, from the perspective of those capabilities being offered by default in the sort of environment that would otherwise disable arbitrary scripting.

Nailing down that definition is, in part, why I'm in here trying to provoke a conversation first, rather than starting out by doing a PoC fork of NoScript!
barbaz wrote: Thu Mar 21, 2024 8:08 pm Actually, "fetch" capability is enabled by default in both Default and Trusted presets.
Weird; I don't like to make bald assertions like this, so I had specifically installed NoScript fresh in a new Firefox profile just to check this before I said it. And the "fetch" capability clearly started out disabled in Default.

But then I restarted Firefox, and checked again, and the "fetch" capability was now enabled in Default.

No idea what that was about.
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Post Reply