Exploiting Zoom whiteboard via the clipboard. Nice find from @spaceraccoon

https://spaceraccoon.dev/analyzing-clipboardevent-listeners-stored-xss/

I Hope This Sticks: Analyzing ClipboardEvent Listeners for Stored XSS

When is copy-paste payloads not self-XSS? When it’s stored XSS. Recently, I reviewed a Zoom’s code to uncover an interesting attack vector.

@gaz @spaceraccoon Nice writeup!

Looking at this with a browser's hat on, I wonder what we could do to make the `convertToText()` mechanism go away. The new `Element.setHTML()` method aims to handle element nodes being added to the DOM. It doesn't do much for attributes... I wonder if that's a pattern common-enough for there to be something here we should be generalizing?

@gaz @spaceraccoon That said, it's a somewhat straightforward wrapper in userland:

```
let temp = document.createElement('div');
temp.setHTML(data);
el.attribute = temp.innerHTML;
```

/cc @freddy

@mikewest @gaz @spaceraccoon @freddy from what I understand, the issue was not due to transporting HTML in an attribute, but rather that later regexes were used to transform that HTML into text-ish form. That processed text later skipped sanitization via DOMPurify (and it would just as likely skip setHTML?). Perhaps DOMPurify wasn't used because it would destroy some of the legitimate payload, it's not clear, but to me it looks like an 'unchecked flows to raw sinks are possible' problem, and not a 'sanitizer is missing' problem. The authors knew untrusted HTML needed sanitizing, they just didn't sanitize there for some reason.

@koto @gaz @spaceraccoon @freddy My read of the last step was that they _did_ use `convertToText()` as a sanitizer for input to the `dangerouslySetInnerHTML` sink, but that their implementation was a regex that didn't handle multi-line strings. :)

Obviously removing the use of the dangerous sink would be better. But given that developers gonna develop, I wonder whether there's anything we can learn from this for the Sanitizer API.

@mikewest @koto @spaceraccoon @freddy Yep Mike is correct because they used "." without /m it wouldn't match new lines.

@gaz @mikewest @spaceraccoon @mikewest @gaz @spaceraccoon I second @koto 's interpretation in that they used DOMPurify in some places, but not in others. imho, the important piece for us to learn is:

A great sanitizer can't help if you forget to use it. A document-wide policy like CSP could enforce things (see Trusted Types).

@freddy @gaz @spaceraccoon @koto I disagree, insofar as I see this as a case of _poor_ sanitization, not _missing_ sanitization. Trusted Types is a critical tool that ensures that _something_ happens to the dangerous input, but it makes no guarantees that the output is inert when it hits a sink.

In this case, they wanted to more or less "de-htmlize" a string, replacing `<br>` with `\n` and etc. Ending that method with a stringified `setHTML()` call with a `*` blocklist would have done the job.

@mikewest @freddy @gaz @spaceraccoon Yes, a striphtmlchars()+nl2br() sanitizer would be nice - but not sure whether it doesn't need additional configuration anyhow. in convertToText there was some HTML entities replacement, for such use cases authors usually want to linkify, add target attributes etc. Even this Mastodon client has some custom icon processing.
@mikewest @freddy @gaz @spaceraccoon All that said, I've written <> removal, HTML escape or nl2br in JS from scratch way too many times, and would rather not do it ever again.
@koto @mikewest @gaz @spaceraccoon Hmm, I like the distinction between the "what" and the "how". Indeed, TT shines a spotlight on the flow from input to sink, but in itself does not guarantee any kind of safety (hence Trusted instead of Safe). Would be nice if we could move towards higher assurance, like safety-guarantees with the help of a Sanitizer...
@freddy @koto @gaz @spaceraccoon For some applications, auto-applying a baseline sanitizer for any HTML sink would be Good Enough™.
@mikewest @koto @gaz @spaceraccoon I believe that's what I had in mind, but is still too ill-defined to write down when I thought about an implicit-use-default-sanitizer mode that could do away with all the Trusted Types policies.
@freddy @mikewest @gaz @spaceraccoon Sanitizer API skips TT checks, because of the xss-free baseline. Implicit Sanitizer would "just work" with a TT CSP. The only uncertainty is how many apps would still function, if innerHTML sanitized implicitly, but that's easy to just crawl and see.
@freddy @gaz @mikewest @spaceraccoon Yeah, easy sanitizer so you don't have to think about your configuration, dependencies, and CSP/TT so you never forget to use it. Now let's just convert all the web to that pattern :)

@gaz @mikewest @spaceraccoon @freddy hm, so they assumed the regexes are strict enough and skipped sanitizing with a full-blown sanitizer afterwards? There was definitely an attempt to sanitize.

DOMPurify was available and used in this codebase, IIUC. If DOMPurify.sanitize(s) was not called, is it more likely that a 2-liner using setHTML would?