惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
B
Blog RSS Feed
W
WeLiveSecurity
I
InfoQ
L
Lohrmann on Cybersecurity
Simon Willison's Weblog
Simon Willison's Weblog
腾讯CDC
S
Schneier on Security
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
Attack and Defense Labs
Attack and Defense Labs
I
Intezer
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
Cisco Talos Blog
Cisco Talos Blog
T
The Exploit Database - CXSecurity.com
S
Securelist
T
Tailwind CSS Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
美团技术团队
Stack Overflow Blog
Stack Overflow Blog
T
Tor Project blog
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
Project Zero
Project Zero
C
Cybersecurity and Infrastructure Security Agency CISA
Apple Machine Learning Research
Apple Machine Learning Research
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
T
The Blog of Author Tim Ferriss
N
News and Events Feed by Topic
小众软件
小众软件
G
Google Developers Blog
F
Full Disclosure
O
OpenAI News
The Last Watchdog
The Last Watchdog
G
GRAHAM CLULEY
TaoSecurity Blog
TaoSecurity Blog
U
Unit 42
Jina AI
Jina AI
S
SegmentFault 最新的问题
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
P
Proofpoint News Feed
Y
Y Combinator Blog
N
News and Events Feed by Topic
K
Kaspersky official blog

Mathias Bynens

A horrifying globalThis polyfill in universal JavaScript JavaScript engine fundamentals: optimizing prototypes JavaScript engine fundamentals: Shapes and Inline Caches Asynchronous stack traces: why await beats Promise#then() ECMAScript regular expressions are getting better! Unicode property escapes in JavaScript regular expressions ES2015 const is not about immutability Valid JavaScript variable names in ES2015 Unicode-aware regular expressions in ES2015 Dear Google, please fix plain text emails in Gmail PBKDF2+HMAC hash collisions explained JavaScript has a Unicode problem Processing Content Security Policy violation reports Loading JSON-formatted data with Ajax and xhr.responseType='json' Reserved keywords in JavaScript How to support full Unicode in MySQL databases How to speedrun Dropbox’s Dropquest 2012 Unquoted font family names in CSS Unquoted property names / object keys in JavaScript Valid JavaScript variable names in ES5 CSS character escape sequences JavaScript’s internal character encoding: UCS-2 or UTF-16? The smallest possible valid (X)HTML documents JavaScript character escape sequences JavaScript foo.prototype.bar notation Ambiguous ampersands HTML element + attribute notation How I detect and use localStorage: a simple JavaScript pattern Unquoted attribute values in HTML and CSS/JS selectors The end-tag open (ETAGO) delimiter Using the oninput event handler with onkeyup/onkeydown as its fallback Everything you always wanted to know about touch icons In defense of CSS hacks — introducing “safe CSS hacks” AirPlay video support in iOS Safari — a bookmarklet Completing Dropbox’s Dropquest 2011 in 60 seconds Using CSS without HTML How to create simple Mac apps from shell scripts Using setTimeout to speed up window.onload Bulletproof JavaScript benchmarks Thoughts on Safari Reader’s generated HTML How to enable Safari Reader on your site? The XML serialization of HTML5, aka ‘XHTML5’ The id attribute got more classy in HTML5 The three levels of HTML5 usage The HTML5 document.head DOM tree accessor Bulletproof HTML5 <details> fallback using jQuery Displaying hidden elements like <head> using CSS Inline <script> and <style> vs. external .js and .css — what’s the size threshold? Using Showdown/PageDown with and without jQuery
Hiding JSON-formatted data in the DOM with CSP enabled
Mathias · 2013-08-27 · via Mathias Bynens

If Content Security Policy is enabled for protection against cross-site scripting attacks (i.e. the unsafe-inline option is not set), the use of inline <script>s is not allowed. In that case, how can we pass server-generated data to the front-end without negatively affecting load time and run-time performance?

Introduction

A common way to pass server-generated JSON-formatted data to the client so that it can be used in JavaScript is the following:

<!-- at the bottom of the HTML document, before `</body>` -->
<script>
	window.data = <?php
		// Note: this is potentially unsafe; see escaping instructions below.
		echo json_encode($data);
	?>;
</script>
<script src="process-data.js"></script>

This results in something like:

<!-- at the bottom of the HTML document, before `</body>` -->
<script>
	window.data = {"foo":"bar&baz","baz":42,"qux":"lorem isn't ipsum","waldo":"2<5"};
</script>
<script src="process-data.js"></script>

Note that in this case, any occurrences of </script in the JSON-formatted data should be escaped as <\/script to avoid closing the <script> element prematurely. Also, <!-​- must be escaped as \u003C!--. Other than that, no special escaping is necessary. In PHP, this boils down to json_encode($data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES);.

The file process-data.js looks something like this:

(function() {

	var process = function(data) {
		// Do stuff with the data, e.g. render charts, modify the DOM, whatever.
	};

	process(window.data);

}());

This technique is simple to implement and doesn’t have a negative impact on the page’s performance. Only a single HTTP request is needed to load the external JavaScript file that processes the data. The data itself is inlined in the HTML so no additional HTTP requests are needed to download it. Moreover, it gets parsed as a JavaScript object right away by the browser’s JavaScript engine — there’s no need to call JSON.parse() at run-time.

However, if Content Security Policy is enabled for protection against cross-site scripting attacks (i.e. the unsafe-inline option is not set), the use of inline scripts is not allowed. In that case, how can we still pass the data along without negatively affecting load time and run-time performance?

Ajax

A naïve solution would be to load the JSON-formatted data through Ajax on page load. The HTML code then looks like this:

<script src="fetch-and-process-data.js"></script>

The file fetch-and-process-data.js looks something like this:

(function() {

	var getJSON = function(url, callback) {
		// Load the data using XHR, and call `callback(data)` when finished.
		// See https://mathiasbynens.be/notes/xhr-responsetype-json for an example implementation.
	};

	var process = function(data) {
		// Do stuff with the data, e.g. render charts, modify the DOM, whatever.
	};

	// Load the data, then process it.
	getJSON('data.json', process);

}());

However, this introduces another HTTP request to load the JSON-formatted data, which negatively impacts load-time performance. This leads to a slower user experience, especially in cases where the HTML document doesn’t really contain any information until the data is loaded (remember Twitter back in 2010?).

What are the alternatives?

We can still inline the JSON-formatted data in the HTML document without using <script> elements (to satisfy CSP) by using a hidden dummy element. Note that the <data> element shouldn’t be used for this purpose, as in this case no human-readable representation of the data is available. Let’s use a <div> element instead:

<div id="important-data" class="hidden-data">
	{"foo":"bar&amp;baz","baz":42,"qux":"lorem isn't ipsum","waldo":"2&lt;5"}
</div>
<script src="read-and-process-data.js"></script>

Note that in this case, the JSON-formatted data needs some extra escaping for security reasons:

  • Any occurrences of < should be escaped as &lt; (at the HTML level) or \u003C (at the JSON level) to avoid accidentally creating unwanted HTML elements and to avoid closing the <div> element prematurely.
  • Any occurrences of & should be escaped as &amp; (at the HTML level) or \u0026 (at the JSON level) to avoid issues with ambiguous ampersands or text that looks like HTML entities in the source data.

No other special escaping is necessary. In PHP, this boils down to json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_SLASHES);.

To prevent browsers from displaying the raw data, CSS can be used:

.hidden-data {
	display: none;
}

(Note that using the hidden HTML attribute instead wouldn’t be appropriate, since the raw data is never to be displayed.)

The file read-and-process-data.js looks something like this:

(function() {

	var getData = function() {
		// Read the JSON-formatted data from the DOM.
		var element = document.getElementById('important-data');
		var string = element.textContent || element.innerText; // fallback for IE ≤ 8
		var data = JSON.parse(string);
		// Clear the element’s contents now that we have a copy of the data.
		element.innerHTML = '';
		return data;
	};

	var process = function(data) {
		// Do stuff with the data, e.g. render charts, modify the DOM, whatever.
	};

	// Get the data, then process it.
	var data = getData();
	process(data);

}());

This approach can be used even when CSP is enabled. That’s a big plus! However, it comes with a few downsides compared to the inline <script>-based technique:

  • As mentioned, an extra round of escaping is needed, which increases the size of the JSON-formatted data.
  • It requires DOM operations (which are generally slow in JavaScript) to read out the data.
  • To convert the data back to a JavaScript object, JSON.parse() must be called at runtime. For backwards compatibility with older browsers, a JSON polyfill is needed.
  • It relies on CSS to hide the raw data.

Alternative: an inline <script> element with a non-JavaScript type

To get rid of the CSS dependency, we could use an element that browsers hide by default instead of a <div>, such as an inline <script> element with a non-JavaScript type attribute (so CSP doesn’t block it or trigger any warnings):

<script type="application/json" id="important-data">
	{"foo":"bar&baz","baz":42,"qux":"lorem isn't ipsum","waldo":"2<5"}
</script>
<script src="read-and-process-data.js"></script>

Note that in this case, any occurrences of </script in the JSON-formatted data should be escaped as <\/script to avoid closing the <script> element prematurely. Since script elements with unknown types are not executed by any browser, there should be no risk in leaving <! unescaped — but feel free to escape it as \u003C! just in case. Other than that, no special escaping is necessary. In PHP, this boils down to json_encode($data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES);.

The read-and-process-data.js script is exactly the same as in the previous example.

A custom data-* attribute

Another solution is to use a custom data-* attribute to hide the JSON-formatted data in the DOM. You could assign this attribute to the <html> or <body> element, but I’d suggest injecting the data before any <script> elements near the closing </body> tag, so that the rest of the content is downloaded and rendered first.

<div id="important-data" class="hidden-data" data-data='{"foo":"bar&amp;baz","baz":42,"qux":"lorem isn&#39;t ipsum","waldo":"2<5"}'></div>
<script src="read-and-process-data.js"></script>

Note that I’ve wrapped the attribute value in single quotes instead of double quotes to avoid having to escape the many double quotes in the JSON-formatted data. Still, the JSON-formatted data needs some extra escaping for security reasons:

  • Any occurrences of ' should be escaped as &#39; or &#x27; (at the HTML level) or as \u0027 (at the JSON level) to avoid breaking out of the HTML attribute value. (If the attribute value is wrapped in double quotes, escape " as &quot; or \u0022 instead.)
  • Any occurrences of & should be escaped as &amp; (at the HTML level) or \u0026 (at the JSON level) to avoid issues with ambiguous ampersands or text that looks like HTML entities in the source data.

No other special escaping is necessary. In PHP, this boils down to json_encode($data, JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_SLASHES);. Note that < and > don’t need escaping in quoted attribute values.

The file read-and-process-data.js looks something like this:

(function() {

	var getData = function() {
		// Read the JSON-formatted data from the DOM.
		var element = document.getElementById('important-data');
		// Note: in modern browsers, you could use `element.dataset.data` instead
		// of `getAttribute('data-data')`.
		var string = element.getAttribute('data-data');
		var data = JSON.parse(string);
		// Remove the attribute now that we have a copy of the data.
		element.removeAttribute('data-data');
		return data;
	};

	var process = function(data) {
		// Do stuff with the data, e.g. render charts, modify the DOM, whatever.
	};

	// Get the data, then process it.
	var data = getData();
	process(data);

}());

Just like the “hidden element” approach, this technique can also be used even when CSP is enabled. Another advantage: since the data is part of an attribute value, it won’t be visible by default.

However, it still has a few downsides compared to the inline <script>-based technique:

  • As mentioned, an extra round of escaping is needed, which increases the size of the JSON-formatted data.
  • It requires DOM operations (which are generally slow in JavaScript) to read out the data.
  • To convert the data back to a JavaScript object, JSON.parse() must be called at runtime. For backwards compatibility with older browsers, a JSON polyfill is needed.
  • CSS may be needed to hide the empty element (in case it inherits border styles, for example), although it won’t be necessary in most cases.

Conclusion

Passing server-generated JSON-formatted data to the client-side for use in JavaScript becomes a bit more complex when CSP is enabled, since inline <script>s are not an option in that case.

With security, performance, and flexibility in mind, the best solution is to hide the JSON-formatted data either in a custom data-* attribute on an element near the closing </body> tag, or in an inline <script> element with type="application/json".

Disclaimer: Enabling CSP is not enough to fully protect your site against client-side security vulnerabilities, although it certainly helps.