【问题标题】:Does the output of JsonConvert.SerializeObject need to be encoded in Razor view?JsonConvert.SerializeObject 的输出是否需要在 Razor 视图中编码?
【发布时间】:2014-05-08 12:28:21
【问题描述】:

我使用 Newtonsoft 库将 C# 对象转换为 JSON。 Newtonsoft.Json.JsonConvert.SerializeObject 的这种使用是否安全,或者是否需要额外的编码?如果需要额外的编码,你有什么建议?

这是我在 Razor 视图中的使用方式:

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

【问题讨论】:

    标签: javascript asp.net-mvc security razor


    【解决方案1】:

    我认为这里不一定不安全,但这取决于数据。如果您的数据已经过清理,如果它来自外部来源,它总是应该这样做的,那么您可能没问题。它进入一个 javascript 对象而不是呈现为 HTML 的事实有点模糊,但它仍然归结为您对输出数据的信任程度。

    【讨论】:

    • 数据不是来自可信来源。另一种看待它的方式,即使它来自受信任的来源,我仍然希望确保输出的任何内容都是有效的对象。
    【解决方案2】:

    您至少需要将“”字符编码为“\u003E”。最后我检查了 JSON.NET 没有在字符串文字中编码这些字符。

    我可能会为此感到震惊,但我这样做的方法是在页面上呈现一个虚拟元素:

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

    然后,在 Javascript 中,从 the-div 元素中提取 data-json 属性值并JSON.parse 它。这样做的好处是您不必担心哪些字符需要特殊编码。 SerializeObject 方法 保证 JSON blob 格式正确,@ 运算符 保证 JSON 中剩余的任何非 HTML 安全字符转换在放入 HTML 属性之前被正确转义(只要属性值用双引号括起来,如上所述)。所以是的,它有点难看,但它可以有效地完全关闭整个类别的漏洞。

    【讨论】:

    • 不应该使用@Html.AttributeEncode(JsonConvert.SerializeObject(Model))吗?
    • '@Html.AttributeEncode' 将对输出进行双重编码。 '@' 运算符本身就是必需的,因为它旨在生成对常规 HTML 和 HTML 属性都安全的输出。
    • @(例如Html.Encode)在这种情况下似乎是可以接受的,但Html.AttributeEncode也是如此。我认为它不会对输出进行双重编码,但我当然想知道我是否错了。见stackoverflow.com/questions/2244079/…
    • @input 被转换为Response.Write(HtmlEncode(input))。因此@Html.AttributeEncode(input) 将被转换为Response.Write(HtmlEncode(HtmlAttributeEncode(input))),从而导致双重编码。我们明确地将@ 设计为对常规 HTML 和 HTML 属性都是安全的,这样开发人员就不必担心 HtmlEncode 和 HtmlAttributeEncode 之间的区别。
    • 啊,你是对的,我假设HtmlAttributeEncode 返回了MvcHtmlString,但它返回了string。那么,有必要使用HtmlAttributeEncode吗?
    【解决方案3】:

    像问题一样单独使用@Html.Raw 绝对是危险的。这是在&lt;script&gt;&lt;/script&gt; 标签中安全输出模型的另一种方法。我按照@Levi的例子依赖浏览器的能力,以及微软的安全特性,想出了这个:

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

    我使用了以下非常简单的测试。如果我只在问题中使用@Html.Raw,则会出现“坏”警报。以这种方式结束,我有有效的 JavaScript 并且没有出现警报。

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

    下一步是将其包装在一个可重用的 HtmlHelper 扩展方法中。

    【讨论】:

      【解决方案4】:

      我制作了这个 JsonConverter,它使用 Microsoft Web Protection Library-library(又名 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>
      

      【讨论】:

        【解决方案5】:

        根据 Torbjörn Hansson 的黄金答案考虑删除一两行代码:

        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>
        

        希望这会有所帮助。

        【讨论】:

          猜你喜欢
          • 2016-02-29
          • 1970-01-01
          • 2011-03-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-04
          相关资源
          最近更新 更多