8

With the Content Security Policy header set on a web server (https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), any inline script is blocked by modern browsers. It is recommended to place all javascript in .js files and configure the policy to authorize the domain where these .js files are hosted.

Fine, but my question is how are we suppose to pass data from the server-side application to the client script ?

For example if I want to call a js function which take server-side value as input, I still have to call the function like the code below (MVC.Net Razor View) in the page body which is blocked.

<body>
...
<input type="button" value="Test" onclick="DoSomething('@ViewData["SomeValue"]');" />
...
</body>

I found some way to pass data in the script src attribute querystring (http://feather.elektrum.org/book/src.html), but i'm not sure it is the best solution. I'm particulary worried about the caching issue of variables in src querystring. Is there a better way to do that ?

Jonathan
  • 1,276
  • 10
  • 35
  • "any inline script is blocked by modern browsers" Never heared of that. – Seblor Jul 12 '17 at 16:08
  • @Seblor: See edit – Jonathan Jul 12 '17 at 16:13
  • 1
    ` For example if I want to call a js function which take server-side value as input, I still have to call the function in the page body which is blocked.` not sure what you are trying to do here. The client makes a Request for "data" and receives "data" and is free to invoke any function after the data is invoked. Is your scenario same as JSONP ? – bhantol Jul 12 '17 at 16:36
  • I don't know much about JSONP, I only want to pass to a javascript function an argument which value comes from the server-side code. For example an MVC.Net View would need to pass a ViewData["anyValue"] to a javascript function call – Jonathan Jul 12 '17 at 17:21
  • https://stackoverflow.com/questions/42922784/what-s-the-purpose-of-the-html-nonce-attribute-for-script-and-style-elements – Prinzhorn Jul 12 '17 at 17:31
  • @Prinzhorn Thanks for your comment, i'm aware of some techniques to allow inline script execution with CSP but that is not my goal. Also, from what I read it didn't seem reliable with every browser/csp version. – Jonathan Jul 12 '17 at 17:49
  • Possible duplicate of [Best practice for embedding arbitrary JSON in the DOM?](https://stackoverflow.com/questions/9320427/best-practice-for-embedding-arbitrary-json-in-the-dom) – Prinzhorn Jul 12 '17 at 18:54
  • @Prinzhorn: Even if the answer of the other question actually solve the problem stated in my question, I don't think it should be treated as a dupplicate. The questions are not the same and I think it is useful to keep mine for someone who would search for this problem related to CSP, i'm sure i won't be the only one to face it. – Jonathan Jul 13 '17 at 12:02

3 Answers3

2

Since it is extremely common to have initial data in the first request, here's a simple way. No need to have an actual script when all you need is data.

<script type="application/json" id="data">{"foo": "bar"}</script>

Then in your JavaScript file

var data = JSON.parse(document.querySelector('#data').innerHTML);
alert(data.foo);
Prinzhorn
  • 22,120
  • 7
  • 61
  • 65
  • It seems a pretty nice and clean solution, the few tests I made so far work well, thanks – Jonathan Jul 12 '17 at 18:36
  • @Jonathan note that I changed textContent to innerHTML – Prinzhorn Jul 13 '17 at 07:34
  • Doesn't this open an attack surface? – MichaelD Feb 17 '19 at 11:51
  • I use this solution myself. But it feels a bit wrong to prevent inline JavaScript to improve security and use inline JSON instead. Alternatively one could create a JSON file on the server for each JSON transfer and let the browser load the file with a script tag. But that is not really elegant. – Tobi G. Aug 02 '19 at 21:40
  • 1
    This is wrong. It doesn't work with the questioner's CSP set to disallow unsafe-inline. The whole point of that is that the browser won't allow scripts, irrespective of their MIME type. That's clear from the CSP definition. I double-checked this today with Chrome and Edge(Chrome). – philw Apr 20 '22 at 09:04
  • @philw there's nothing executed here, we simply abuse the DOM to hold some data for us. This is a common pattern (e.g. both GitHub and StackOverflow do it) and there is no CSP violation happening. It's common to use a script tag for semantic reasons, but you could equally put the JSON into a div. But you don't have to, it works. – Prinzhorn Apr 20 '22 at 16:18
1

Instead of having the server output inline JavaScript with values dynamically inserted, you would need to have your JavaScript call an endpoint on the server (e.g. a REST API) that returns the data it needs. The restriction is only on inline scripts; you can still use XMLHttpRequest to get arbitrary data from the server at runtime.

Alternatively, you can serve a dynamically generated js file that would work just like your inline script, but executed separately. This is a possibility but a bit of a hack, and with no real advantages.

Adrian
  • 42,911
  • 6
  • 107
  • 99
  • I thought of that too, but I was worried about the extra http request to the server which in situation of charge may hurt performance – Jonathan Jul 12 '17 at 17:44
  • It's incredibly common in modern web applications, plus it's your only option - one way or another you'll have to make a server call to get the data if you can't do it inline. – Adrian Jul 12 '17 at 17:50
0

Five years later, but here's what I've decided on as a general strategy:

For server values going to client scripts run at page load time I use disabled input fields in the page:

 <input id='init_data' type='hidden' disabled value='@ViewData["SomeValue"]'>

which avoids any inline javascript, and then grab the value after page load has happened, e.g.

$(document).ready(function(){
  let myValue = $("#init_data").val();
  DoSomething(myValue);
});

For replacing 'onClick' type events which need some server generated values, I replace the onClick attribute with 'data-click' and add an 'event_xxxx' class to attach the trigger event to:

 <input class='event_xxxx' type='button' data-click='@ViewData["SomeValue"]'>

$(document).ready(function(){
    $(".event_xxxx").click(function() {
      let myData = $(this).attr('data-click');
      <do things>
    } ;
});

I use an id for initial data, as that typically relates to a single event happening at page load, whereas my onClicks are generally for multiple items, so I attach the same class to those.

Mike
  • 33
  • 6