@Html.Raw(json) cross-site scripting trap

Often, in ASP.NET & JavaScript apps it is required to pass the server-side model to client-side without AJAX requests. For that, the server-side model is converted to JSON and puts somewhere in JavaScript. You know that. Let’s have a quiz.

What the following ASP.NET MVC Razor view will show?

@using Newtonsoft.Json
@{
    var serverModel = new[]
    {
        new {Name = "<script>alert(1);</script>"},
        new {Name = "<script>alert(2);</script>"}
    };
    var json = JsonConvert.SerializeObject(serverModel);
}
<html>
<body>

<pre id="content"></pre>
<script>
var clientModel = @Html.Raw(json);
var content = document.getElementById("content");
content.innerText = clientModel.map(e => e.Name).join("\n");
</script>

</body>
</html>

Or just pure HTML/JavaScript page. What about this?

<html>
<body>

<pre id="content"></pre>
<script>
var clientModel = [
    {"Name":"<script>alert(1);</script>"},
    {"Name":"<script>alert(2);</script>"}
];
var content = document.getElementById("content");
content.innerText = clientModel.map(e => e.Name).join("\n");
</script>

</body>
</html>

Oops, it gives 2 in the alert window! Hello, cross-site scripting. It is not obvious at all, at least for me. Moreover, I have seen such a pattern of passing the server-side model to client-side quite often. For example: link #1, link #2, link #3.

It turned out that browser’s HTML parser doesn’t care whether </script> tag is inside a string or not, most probably it is due to optimization. Stack Overflow proof.

How can we fix such vulnerability? I will give you a few options.

Use StringEscapeHandling.EscapeHtml setting

Newtonsoft.Json has a built-in feature to escape < > characters by passing StringEscapeHandling.EscapeHtml as a setting during serialization. Stack Overflow answer.

var json = JsonConvert.SerializeObject(serverModel, new JsonSerializerSettings
{
	StringEscapeHandling = StringEscapeHandling.EscapeHtml
});

Encode JSON via HttpUtility.JavaScriptStringEncode

.NET has a built-in feature HttpUtility.JavaScriptStringEncode to encode JSON but it returns a string so JSON.parse is required. Stack Overflow answer.

var clientModel = JSON.parse("@Html.Raw(HttpUtility.JavaScriptStringEncode(json))");

Serialize via Json.Encode

.NET has a built-in Json.Encode it serializes and encodes at the same time. However, be careful JavaScriptSerializer is used instead of Newtonsoft.Json, so some unexpected behavior can take place.

Welcome, I reproduced this vulnerability in Gaev.Blog.Examples.ScriptInString. You can also find there proposed fixes.