nibtime / next-safe-middleware

Strict CSP (Content-Security-Policy) for Next.js hybrid apps https://web.dev/strict-csp/
https://next-safe-middleware.vercel.app
MIT License
79 stars 20 forks source link

isomorphic component (Browser + SSR) for HTML sanitization paired with CSP #41

Open nibtime opened 2 years ago

nibtime commented 2 years ago

Motivation

Where is the isomorphic <SafelySetInnerHtml> component? I think this package is a good context to provide this component and pair it with CSP

Idea

HTML Sanitization has two sides: The client-side, that prevents XSS DOM -> Database, and the server-side, that prevents Database -> XSS DOM. The client-side could be implemented with DOMPurify and paired with trusted-types spec, the server-side with sanitize-html and paired with HTML preprocessing of getCspInitialProps (#40).

Resources

https://www.npmjs.com/package/dompurify https://www.npmjs.com/package/sanitize-html https://github.com/cure53/DOMPurify#what-about-dompurify-and-trusted-types

aheimlich commented 2 years ago

I'd recommend looking into html-react-parser. I've used it in combination with dompurify before. Browser support for trusted-typesreally isn't there, yet.

nibtime commented 2 years ago

Hi @aheimlich

thanks for the recommendation! I will have a look into it when I find time to tackle and experiment with this.

trusted-types seems a Chromium/Google exclusive thing. If I find a way to integrate with trusted-types without too much hassle, I'll do it, so it is just gonna be an additional layer of security for supported browsers and those without support will simply ignore it.

strict-dynamic support as a whole also isn't as good in reality as caniuse.com says, as Firefox and Safari don't fully conform to CSP-3 spec, e.g. they mess up SRI validation on scripts with src attribute in conjunction with strict-dynamic, so they don't really support Hash-based strict CSP etc., whereas all Chromium-based browsers (Chrome, Opera, Edge) do.

Also, a (strict) CSP can never provide uniform protection for all visitors (the IE 11 people won't have it) that's why sanitization is still very important, and making that really convenient and easy and play together with CSP would be a good complement I thought.

A good help for me to think the right way about what CSPs can and can't provide is the comparison with 2FA and passwords. Users that opt into it (use a browser that supports strict CSP) dramatically decrease the chance of getting their account compromised (becoming a victim of XSS), users that don't set it up (use a browser that doesn't support strict CSP) don't have the additional layer of security. Also 2FA doesn't mean you should use weak passwords (don't sanitize dynamic user data).

Then there can also be something like enforcing 2FA on the app level, so all your users have to set up 2FA before they are able to use the app.

The CSP equivalent here would be: Redirect all visitors with an unsupported browser to a page with a prompt to download a supported browser to visit the site. This way only visitors with browsers that guarantee strict CSP support can ever visit your actual website, at the cost that a lot of users won't do it and bounce. Something that could be done very easily implemented with middleware.

aheimlich commented 2 years ago

To be clear, I wasn't suggesting using html-react-parser as a replacement for XSS filtering (whether it be via dompurify, santize-html, or some other library). In fact, the package specifically warns against doing exactly that. I was suggesting using it specifically as a replacement for trusted-types. What I'm doing currently is something like:

import React from "react";
import { sanitize } from "dompurify";
import HtmlReactParser from "html-react-parser";

function MyComponent(props) {
  const sanitizedHtml = sanitize(props.rawHtml);
  return HtmlReactParser(sanitizedHtml);
}

html-react-parser also has a replace option that allows you do things like adding rel="noreferrer noopener" to all external links, or replacing all <img> tags with the next/image component.

nibtime commented 2 years ago

@aheimlich

no worries, I didn't assume you meant it as a replacement for sanitization, I understood it as a suggestion to pair with sanitization for an isomorphic component with good DX.

This replacement option would be interesting here: https://next-safe-middleware.vercel.app/#contributors In the MDX, it dangerously sets a string with the contributors table that gets generated by all-contributors bot/cli. I wondered how you could replace the image avatars with optimized next/image and html-react-parser seems to be exactly the tool you need for something like that.

I have some ideas I want to try out for this, I will tackle it after #66. This would then become an extra package @strict-csp/trusted-html or something like that.

nibtime commented 2 years ago

Also a note on this: The SSR sanitization in terms of strict CSP will only be required if you process plain HTML for sources, like style elements and style attributes, as they will be trusted during SSR. Processing plain HTML is configurable in getCspInitialProps, so you can always opt out from it in case of security concerns. This could lead to SSR injections of malicious stuff from data fetching methods, but CSR injections would still be blocked.

For scripts, however, this lib hooks into Next SSR in way, that it observes scripts from the perspective of the framework, so malicious scripts injected in HTML during SSR will never become trusted. A huge advantage over generic post-build processing of <script> tags in HTML. There also isn't an option to change that, the lib follows the assumption that you idiomatically use next/script and then everything will work and be automatic.

The effect of this: even if you dangerously set HTML from data fetching (getStaticProps, getServerSideProps, getInitialProps) and it include malicious scripts, they will be blocked, so scripts are always safe via strict-dynamic, both for SSR and CSR, even without sanitization.

However, browsers without strict-dynamic support don't have this protection, why sanitization is still important, both for CSR (dom-purify) and SSR (sanitize-html ...). trusted-types would just be a complement for new supported browsers as the intersection of browsers that support trusted-types but don't support strict-dynamic is empty.

With Safari supporting strict-dynamic now and the fast auto-updating of Apple, there is a whopping + ~15% of browsers that benefit from Strict CSP protection, that is why I also put a lot of effort in making #63 work.