通过Javascript输出JSON内容时,我是否应该在服务器端或客户端转义HTML?

时间:2015-09-28 19:54:30

标签: javascript json escaping xss

我有一个应用程序,它包含一个用PHP编写的服务器端REST API,以及一些客户端Javascript,它使用这个API并使用它生成的JSON来呈现页面。所以,这是一个非常典型的设置。

REST API提供的数据是“不受信任的”,因为它从数据库中获取用户提供的内容。因此,例如,它可能会获取类似的内容:

{
    "message": "<script>alert("Gotcha!")</script>"
}

显然,如果我的客户端代码直接将其呈现到页面的DOM中,我就会创建一个XSS漏洞。因此,此内容需要首先进行HTML转义。

问题是,当输出不受信任的内容时,我是否应该逃避服务器端或客户端的内容?即,我的API应该返回原始内容,然后使客户端Javascript代码有责任逃避特殊字符,或者我的API应该返回&#34; safe&#34;含量:

{
    "message": "&lt;script&gt;alert(&#039;Gotcha!&#039;);&lt;\/script&gt;"
}

已经逃过了?

一方面,似乎客户端不应该担心来自我的服务器的不安全数据。另一方面,有人可能会争辩说,当我们确切知道如何消耗数据时,输出应始终在最后一刻被转义。

哪种方法是正确的?

注意:有很多关于处理输入的问题,是的,我知道客户端代码总是可以被操纵。这个问题是关于我的服务器输出数据,这可能是不可信任的。

更新:我调查了其他人正在做的事情,似乎有些REST API倾向于发送&#34;不安全&#34; JSON。 Gitter的API实际上发送了两者,这是一个有趣的想法:

[
    {
        "id":"560ab5d0081f3a9c044d709e",
        "text":"testing the API: <script>alert('hey')</script>",
        "html":"testing the API: &lt;script&gt;alert(&#39;hey&#39;)&lt;/script&gt;",
        "sent":"2015-09-29T16:01:19.999Z",
        "fromUser":{
            ...
        },"unread":false,
        "readBy":0,
        "urls":[],
        "mentions":[],
        "issues":[],
        "meta":[],
        "v":1
    }
]

请注意,他们会在text密钥中发送原始内容,然后在html密钥中发送HTML转义版本。这不是一个坏主意,IMO。

我接受了答案,但我不相信这是一个干枯的问题。我想鼓励进一步讨论这个主题。

4 个答案:

答案 0 :(得分:14)

仅在客户端转义

在客户端逃避的原因是安全性:服务器的输出是客户端的输入,因此客户端不应该信任它。如果您认为输入已经被转义,那么您可能会通过例如恶意反向代理打开客户端攻击。这与您应该始终在服务器端验证输入的原因没有什么不同,即使您还包括客户端验证。

在服务器端转义的原因是关注点分离:服务器不应该假设客户端打算将数据呈现为HTML 。服务器的输出应尽可能与媒体无关(当然,考虑到JSON和数据结构的约束),以便客户端可以最轻松地将其转换为所需的任何格式。

答案 1 :(得分:3)

对于输出转义:

我建议您阅读XSS Filter Evasion Cheat Sheet

为了正确防止用户,您最好不仅要逃避,而且在转义之前使用适当的防XSS库进行过滤。与htmLawedHTML Purifierthis thread中的任何内容类似。

每当您要在网络项目中显示时,都应该对用户输入的数据进行恕我直言sanitizing

  

我应该逃避服务器端或客户端的内容吗?即,我的API应该返回原始内容,然后使客户端Javascript代码有责任转义特殊字符,或者我的API应该返回“安全”内容:

最好返回已经转义的和xss纯化的内容,所以:

  1. 获取原始数据并从服务器上的xss中清除
  2. 逃避它
  3. 返回JavaScript
  4. 此外,您应该注意一件重要的事情,例如网站负载和读/写余额:例如,如果您的客户端输入一次数据并且您要向1M用户显示此数据,您更喜欢以下内容:在写入之前运行保护逻辑(保护输入)每次读取一百万次(保护输出)?

    如果您要在页面上显示1K帖子并在客户端上逐个转发,那么它在客户端的手机上的效果如何?最后一个将帮助您选择在客户端或服务器上保护数据的位置。

答案 2 :(得分:0)

这个答案更侧重于争论是否进行客户端转义与服务器端,因为OP似乎意识到反对输出与输出的转义。

为什么不逃避客户端?

我认为在javascript级别转义并不是一个好主意。如果清理脚本中出现错误,它将无法运行,然后危险的脚本将被允许运行,这只是我头脑中的一个问题。因此,您已经引入了一个向量,攻击者可以尝试创建输入来破坏JS清理程序,以便允许它们的普通脚本运行。我也不知道在JS中运行的任何内置AntiXSS库。我确信有人已经创建了一个,或者可以创建一个,但是已经建立了服务器端的示例,这些示例更值得信任。值得一提的是,在JS中编写适用于所有浏览器的清洁剂并非易事。

好的,如果你两个都逃脱怎么办?

逃离服务器端和客户端只会让我感到困惑,并且不应该提供任何额外的安全性。你提到了双重逃避的困难,我之前经历过这种痛苦。

为什么服务器端足够好?

逃离服务器端应该足够了。你关于尽可能晚做这件事的观点是有道理的,但我认为逃避客户端的弊端远远超过了你可能获得的任何微小好处。威胁在哪里?如果您的站点和客户端之间存在攻击者,则客户端已经受到攻击,因为他们可以根据需要发送带有脚本的空白html文件。您需要尽力发送安全的东西,而不仅仅是发送工具来处理您的危险数据。

答案 3 :(得分:0)

TLDR; 如果您的API要传递格式信息,则应输出HTML编码的字符串。 警告:任何消费者都需要信任您的API而不是输出恶意代码。内容安全政策也可以为此提供帮助。

如果您的API只输出纯文本,那么客户端的HTML编码(在纯文本中<在任何输出中也意味着<。)

时间不长,未读完:

如果您同时拥有API和Web应用程序,则无论哪种方式都可以接受。只要您没有在没有十六进制实体编码的情况下将JSON输出到HTML页面like this

<%
payload = "[{ foo: '" + foo + "'}]"
%>
    <script><%= payload %></script>

然后,服务器上的代码是&更改为&amp;还是浏览器中的代码将&更改为&amp;并不重要。

让我们从您的问题中提取示例:

[
    {
        "id":"560ab5d0081f3a9c044d709e",
        "text":"testing the API: <script>alert('hey')</script>",
        "html":"testing the API: &lt;script&gt;alert(&#39;hey&#39;)&lt;/script&gt;",
        "sent":"2015-09-29T16:01:19.999Z",

如果从api.example.com返回上述内容并且您从www.example.com调用它,则当您控制双方时,您可以决定是否要使用纯文本,&#34; {{1} }&#34;,或格式化的文字,&#34; text&#34;。

重要的是要记住,插入html的任何变量都是HTML编码的服务器端。并且还假设已经执行了正确的JSON编码,这会阻止任何引号字符中断或更改JSON的上下文(为简单起见,上面未显示)。

html将使用Node.textContenttext作为Element.innerHTML插入到文档中。使用html会导致浏览器忽略可能存在的任何HTML格式和脚本,因为Node.textContent等字符实际上被视为在页面上输出为<

注意您的示例显示用户内容作为脚本输入。即,用户已在您的应用程序中键入<不是 API生成的。如果你的API实际上想要输出标签作为其功能的一部分,那么它必须将它们放在JSON中:

<script>alert('hey')</script>

然后您的"html":"<u>Underlined</u>" 必须才输出文字而不进行格式化:

text

因此,在向Web应用程序使用者发送信息时,您的API不再传输富文本,而只传输纯文本。

但是,如果第三方使用了您的API,那么他们可能希望将您的API中的数据作为纯文本获取,因为他们可以在客户端设置"text":"Underlined" (或HTML编码)他们自己知道这是安全的。如果您返回HTML,那么您的消费者需要相信您的HTML不包含任何恶意脚本。

因此,如果以上内容来自api.example.com,但您的消费者是第三方网站,例如www.example.edu,那么他们可能会感觉更舒服地接受Node.textContent而不是HTML。在这种情况下,您的输出可能需要更精细地定义,而不是输出

text

你会输出

"text":"Thank you Alice for signing up."

或类似的,因此您不再在JSON中定义布局,您只是传达客户端的信息,以便使用他们自己的风格进行解释和格式化。为了进一步说明我的意思,如果你的所有消费者都得到了

[{ "name", "alice",
"messageType": "thank_you" }]

并且他们希望以粗体显示名称,如果没有复杂的解析,他们完成此操作将非常棘手。但是,通过在粒度级别定义API输出,消费者可以像变量一样获取相关的输出,然后应用他们自己的HTML格式,而不必相信您的API只输出粗体标签("text":"Thank you Alice for signing up." )和不输出恶意JavaScript(无论是来自用户还是来自您,如果您确实是恶意的,或者您的API已被泄露)。