1 of 50

Making of:

The Sanitizer API

Frederik Braun

Staff Security Engineer at Mozilla

@freddyb

1

Questions? Comments?

✉️ freddyb@mozilla.com

1

2 of 50

2

2

3 of 50

3

3

4 of 50

Frederik Braun

@freddyb

Staff Security Engineer

moz://a

4

4

5 of 50

foo.innerHTML = evil

DOM-based XSS

5

6 of 50

As an

Aside

6

7 of 50

How people fix DOM-based XSS

Encode and escape

Sanitize

Use textContent

Replace dangerous stuff with e.g., HTML entities.

If you want to allow some safe subset of HTML, use a Sanitizer

Vulnerable code line doesn’t need HTML?

Use textContent instead.

Done!

7

7

8 of 50

Fixing DOM-based XSS

Encode and escape

Sanitize

Use textContent

Replace dangerous stuff with e.g., HTML entities.

If you want to allow some safe subset of HTML, use a Sanitizer

Vulnerable code line doesn’t need HTML?

Use textContent instead.

Done!

You are here 📌

8

8

9 of 50

What is a Sanitizer?

9

10 of 50

What’s in a Sanitizer?

2. Sanitize

1. Parse

(3. Serialize)

<p>Hello World!

<img src=x

onerror=alert(1)>

<p>Hello World!

<img src="x">

</p>

10

10

11 of 50

Your Sanitizer is mostly an HTML Parser!

11

12 of 50

A Sanitizer API

12

13 of 50

Goals

  1. Defining “Safe HTML”

  • Safe by Default

  • No Parsing Mistakes

  • Configurable

  • Responsibility shift to the browser

13

14 of 50

foo.innerHTML = evil

DOM-based XSS

14

15 of 50

First Idea

mySanitizer = new Sanitizer(options)

mySanitizer.sanitize() // String

15

16 of 50

foo.innerHTML =

mySanitizer.sanitize(evil)

16

17 of 50

What’s in a Sanitizer?

2. Sanitize

1. Parse

3. Serialize

<p>Hello World!

<img src=x

onerror=alert(1)>

<p>Hello World!

<img src="x">

</p>

17

17

18 of 50

What’s in "foo.innerHTML=" ?

2. Append

1. Parse

<p>Hello World!

<img src=x

onerror=alert(1)>

18

18

19 of 50

Now we’re using TWO HTML Parsers?!

19

20 of 50

API: Revision 1

mySanitizer = new Sanitizer(options)

mySanitizer.sanitize() // DocFragment

mySanitizer.sanitizeToString() // String

20

21 of 50

foo.append(mySanitizer.sanitize(evil))

21

22 of 50

Nothing good is designed

in a vacuum

Looking for Bugs here. Anyone got some bugs?

22

23 of 50

Sanitizer is less expressive than innerHTML

01

https://github.com/WICG/sanitizer-api/issues/42

Reported by Anne van Kesteren (@annevk)

23

24 of 50

innerHTML

With the Sanitizer

Without the Sanitizer

tableElement.append(

mySanitizer.sanitize(sameInput))

tableElement.innerHTML =

"<tr><td>some cell</td></tr>"

24

24

25 of 50

Parsing HTML fragments

§ 13.4 Parsing HTML fragments

The following steps form the HTML fragment parsing algorithm. The algorithm takes as input an Element node, referred to as the context element, which gives the context for the parser, as well as input, a string to parse, and returns a list of zero or more nodes.

(...)�

  1. Set the state of the HTML parser's tokenization stage as follows, switching on the context element:

(long list of various html elements that cause different parsing behaviors)

25

25

26 of 50

Fragment parsing without context

1. Fragment-parse into <body>

<tr><td>some cell

</tr></td>

26

26

27 of 50

Sanitizer Bypass with iframe srcdoc

02

https://bugzilla.mozilla.org/show_bug.cgi?id=1669945

Reported by Michał Bentkowski (@SecurityMB)

27

28 of 50

Michał’s Bypass: The code

<iframe id=ifr></iframe>

<script>

const bypass =

`<svg><font color><title><u rel="</title><img src onerror=alert(document.domain)>">`;

ifr.srcdoc = new Sanitizer().sanitizeToString(bypass);

</script>

28

28

29 of 50

Parsing within sanitizeToString

<iframe id=ifr></iframe>

<script>

const bypass =

`<svg><font color><title><u rel="</title><img src onerror=alert(document.domain)>">`;

ifr.srcdoc = new Sanitizer().sanitizeToString(bypass);

</script>

└ <svg:svg>

└ <svg:font color="">

└ <svg:title>

└ <html:u rel="</title><img src onerror=alert(1)>">

29

29

30 of 50

String returned from sanitizeToString

└ <svg:svg>

└ <svg:font color="">

└ <svg:title>

└ <html:u rel="</title><img src onerror=alert(1)>">

<svg>

<font color="">

<title>

<u rel="</title>

<img src onerror=alert(document.domain)>"></u></title></font></svg>

30

30

31 of 50

Parsing into the iframe srcdoc

<svg>

<font color="">

<title>

<u rel="</title>

<img src onerror=alert(document.domain)>"></u></title></font></svg>

├ <svg:svg>

└ <html:font color="">

├ <html title>

│ └ #text: <u rel="

<html:img src="" onerror="alert(document.domain)"/>

└ #text: ">

31

31

32 of 50

Burn all Parsers!

32

33 of 50

Parsers!!!1

33

33

34 of 50

foo.setHTML(evil, { sanitizer: mySanitizer })

34

35 of 50

foo.setHTML(evil)

35

36 of 50

Security Considerations

Server-Side Reflected and Stored XSS

DOM clobbering

XSS with Script gadgets

Mutated XSS

36

37 of 50

Server-Side XSS

The Sanitizer API is just for DOM-based XSS.

37

38 of 50

DOM clobbering

<form id=f>

<input id=childNodes>foo

<input id=childNodes>bar

<input type=text

value="hidden-from-js">

38

39 of 50

DOM clobbering

Sanitizer API is looking through clobbered properties

Preventing it in your app is currently out of scope.

You could configure the sanitizer to disallow e.g., name & id attributes.

39

40 of 50

XSS with Script gadgets

<button data-html="injection here"

data-html-enabled="true"></button>

40

41 of 50

XSS with Script gadgets

The Sanitizer can not prevent these attacks.

But you can disallow e.g., data- or role attributes if you customize it according to your framework(s)

41

42 of 50

mXSS

<img src=" test.jpg" alt="``onload=xss()" />

<IMG alt=`` onload=xss() src="test.jpg">

42

43 of 50

mXSS

The Sanitizer offers help against mXSS.

Parse at your own peril.

43

44 of 50

Nothing good is developed without feedback.

We’re still not done here. Gimme moar bugs.

44

45 of 50

Bounties

  1. Enable the Sanitizer
  2. Go to about:config. Toggle dom.security.sanitizer.enabled

  • about://flags#sanitizer-api
  • or “Experimental Web Platform Features

  • Go to empty web page and

open Developer Tools

  • document.body.setHTML(evil)

  • Profit

45

46 of 50

Discussion

46

47 of 50

Coding

  • Add more tests to web-platform-tests

  • Polyfill at

https://github.com/mozilla/sanitizer-polyfill/

47

48 of 50

Burn all Parsers!

48

49 of 50

If you need an HTML Parser,

make sure you pick the right one.

49

50 of 50

Thank you!

Frederik Braun (@freddyb)

Staff Security Engineer at Mozilla

Questions? Comments?

✉️ freddyb@mozilla.com

50