Должен ли файл JsonConvert.SerializeObject быть закодирован в режиме Razor?

Я использую библиотеку Newtonsoft для преобразования объектов С# в JSON. Используется ли это Newtonsoft.Json.JsonConvert.SerializeObject безопасно или требуется дополнительная кодировка? Если требуется дополнительная кодировка, что вы предлагаете?

Вот как я использую его в представлении Razor:

<script type="text/javascript">
    var jsModel = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model))
</script>

Ответ 1

Вам, по крайней мере, потребуется выполнить дополнительную кодировку '<' символ "\ u003C" и " > "; символ "\ u003E". Последнее, что я проверил, JSON.NET не кодировал эти символы в строковых литералах.

Я, вероятно, собираюсь получить flak для этого, но способ, которым я это сделаю, - отобразить на странице фиктивный элемент:

<div id="the-div" data-json="@JsonConvert.SerializeObject(Model)" />

Затем в Javascript извлеките значение атрибута data-json из элемента-div и JSON.parse. Преимущество этого в том, что вам не нужно беспокоиться о том, какие символы требуют специальной кодировки. Метод SerializeObject гарантирует, что кадр JSON хорошо сформирован, а оператор @ гарантирует, что все оставшиеся небезопасные символы, оставшиеся от JSON преобразование должным образом экранировано перед тем, как быть помещенным в атрибут HTML (при условии, что значение атрибута окружено двойными кавычками, как указано выше). Так что да, это немного уродливее, но это эффективно при полном закрытии всего класса уязвимостей.

Ответ 2

Использование @Html.Raw самостоятельно, как и вопрос, безусловно, опасно. Вот еще один способ безопасного вывода модели в теги <script></script>. Я следовал примеру @Levi, чтобы зависеть от возможностей браузера, а также от функций безопасности Microsoft, и придумал следующее:

var jsModel = JSON.parse("@Html.Raw(HttpUtility.JavaScriptStringEncode(
    JsonConvert.SerializeObject(Model)
))");

Я использовал следующий очень простой тест. Если бы я использовал только @Html.Raw, как в вопросе, появляется предупреждение "Bad". Таким образом, у меня есть действующий JavaScript и предупреждение не появляется.

var jsModel = JSON.parse("@Html.Raw(HttpUtility.JavaScriptStringEncode(
    JsonConvert.SerializeObject(new {
        Test = "</script><script>var test = alert('Bad')</script>"
    })
))");

Следующим шагом будет обернуть это в многоразовый метод расширения HtmlHelper.

Ответ 3

Я сделал этот JsonConverter, который кодирует все строки библиотекой Microsoft Web Protection Library (aka AntiXSS-library) (http://wpl.codeplex.com/):

/// <summary>
/// To be used when you're going to output the json data within a script-element on a web page.
/// </summary>
public class JsonJavaScriptEncodeConverter : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return reader.Value;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        writer.WriteRawValue(Microsoft.Security.Application.Encoder.JavaScriptEncode((string)value, true));
    }
}

Использование:

<script type="text/javascript">
    var jsModel = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(Model, new JsonJavaScriptEncodeConverter()))
</script>

Ответ 4

Я не думаю, что это обязательно небезопасно здесь, но это зависит от данных. Если данные были подвергнуты санитарной обработке, что всегда необходимо, если они поступают из внешнего источника, то вы, вероятно, в порядке. Тот факт, что он входит в объект javascript и не отображается как HTML, немного затушевывает все, но до сих пор он доходит до вашего уровня доверия с выводимыми данными.

Ответ 5

Мысль опустить строку кода или два на основе золотого ответа Торбьёрна Хансона:

public static class U
{
    private static readonly GeneralPurposeJsonJavaScriptEncodeConverter _generalEncoder = new GeneralPurposeJsonJavaScriptEncodeConverter();
    static public IHtmlString Js(this object obj) => new HtmlString(JsonConvert.SerializeObject(obj, _generalEncoder));

    private sealed class GeneralPurposeJsonJavaScriptEncodeConverter : JsonConverter //0
    {
        private static readonly Type TypeOfString = typeof(string);

        public override bool CanConvert(Type objectType) => objectType == TypeOfString;
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => reader.Value;
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteRawValue(Microsoft.Security.Application.Encoder.JavaScriptEncode((string) value, emitQuotes: true)); //1
    }
    //0 https://stackoverflow.com/a/28111588/863651   used when we need to burn raw json data directly inside a script element of our html like when we do when we use razor
    //1 note that the javascript encoder will leave nonenglish characters as they are and rightfully so   apparently the industry considers text in html attributes and inside
    //  html text blocks to be a battery for potential xss exploits and this is why the antixsslib applies html encoding on nonenglish characters there but not here   one
    //  could make the claim that using unicode escape sequences here for nonenglish characters could be potentionally useful if the clients receiving the server html response
    //  do not support utf8   however in our time and age clients that dont support utf8 are rarer than hens teeth so theres no point going this direction either
}

И вот несколько примеров того, как его использовать (а когда не использовать):

<span>
            @someStringWhichMightContainQuotes @* no need to use .Js() here *@
</span>

@* no need to use .Js() here *@
<input value="@someStringWhichMightContainQuotes" />

@* no need to use .Js() here either - this will work as intended automagically *@
@* notice however that we have to wrap the string in single-quotes *@
<button   onclick="Foobar( '@("abc  \"  '  ")'  )"> Text </button>

@* The resulting markup will be:
            <button onclick="Foobar(  'abc &quot; &#39; '  )"> Text </button>
Which will work as intended *@

И последнее, но не менее важное:

<script type="text/javascript">
    someJsController.Init({
        @* containerSelector: "#@(containerId.Js())",  ← wrong  dont do this *@

        containerSelector: "#" + @(containerId.Js()),  @* ← correct  *@
        containerSelector2: @($"#{container2Id}".Js()),  @* ← even better do this for readability *@

        simpleString: @(Model.FilterCode.Js()), @* all these will serialize correctly *@
        someArray: @(Model.ColumnsNames.Js()), @* by simply calling the .js() method *@
        someNumeric: @(Model.SelectedId.Js()),
        complexCsharpObject: @(Model.complexCsharpObject.Js())
    });
</script>

Надеюсь, что это поможет.