11 comments

[ 3.6 ms ] story [ 43.1 ms ] thread
Discussing why parsing HTML SCRIPT elements is so complicated, the history of why it became the way it is, and how to safely and securely embed JSON content inside of a SCRIPT element today.
If you're evaluating JSON as JavaScript, you also need to make sure none of the objects have a key named "__proto__", or else you can end up with some strange results.

(This is related to the 'prototype pollution' attack, although searching that phrase will mostly give you information about the more-dangerous variant where two objects are being merged together with some JS library. If __proto__ is just part of a literal, the behavior is not as dangerous, but still surprising.)

Wait can someone explain why a script tag inside a comment inside a script tag needs to be closed, while a script tag inside a script tag without a comment does not? They explained why comments inside script tags are a thing, but nothing further than that.
> Not so fast, things are about to get messy

That ship sailed several paragraphs ago, when <script> got special treatment by the HTML parser. Too bad we couldn't all agree to parse <![CDATA[...]]> consistently, or, you know, just &-escape the text like we do /everywhere else/ in HTML.

This reminded me of how in the early 2000s I was taught to enclose the content of SCRIPT tags in HTML comments, e.g.

  <script language="JavaScript"><!--
  
  // script contents

  -->

  </script>
I would say avoid trying to understand arcane nuances better than the adversary. Assume they've simultaneously got more time on their hands and sat on the relevant standards committees. Adopt a strategy that's robust to having missed a small nuance in the standard or in the particular implementation by this or that browser. (That doesn't mean there isn't value in a blog post enumerating the edge cases, of course.)

Kaminsky described a very simple and nearly-universal technique to deal with escaping/injection issues. Encode the embedded data as base64 and decode it on the client side. This projects arbitrary data into a fixed, known domain (generally `[a-zA-Z0-9+/]*`) which you can ensure is free from control characters. (You may need to use a particular variant to achieve this, eg for URLs the last characters used are generally `-_` because both + and / are significant in that context.)

After decoding, you can pass it to JSON.parse().

"<"+"/script>", its a matter of parsing not value
Is there any specific reason to use JSON_UNESCAPED_SLASHES, or is it just because it becomes unnecessary? The article mentions it several times, but never explains why to use it.
> Imagine if script tags required HTML escaping:

There are two situations in which it does.

① XML syntax, which is absolutely still a thing:

  data:application/xhtml+xml,<html xmlns="http://www.w3.org/1999/xhtml"><script>console.log( 1 &gt; 0 &amp;&amp; 0 &lt; 1 )</script></html>
② Inside an SVG <script> element in HTML syntax:

  data:text/html,<svg><script>console.log( 1 &gt; 0 &amp;&amp; 0 &lt; 1 )</script></svg>
What about CDATA; which XML and XHTML support? HTML5 does not support CDATA.

CDATA: https://en.wikipedia.org/wiki/CDATA

  <![CDATA[
  ]]>
This would work for XHTML but not HTML5 IIUC:

  <script>
  <![CDATA[
  x = {"<!--":""};
  ]]>

  <![CDATA[
  {{json.dumps(["<!--"])}}
  ]]>
  </script>