当通过Javascript输出JSON内容时,我应该HTML在服务器端还是在客户端转义?
When outputting JSON content via Javascript, should I HTML escape on the server or client side?
我有一个应用程序,它包含一个用 PHP 编写的服务器端 REST API,以及一些使用此 API 并使用的客户端 Javascript它生成的 JSON 用于呈现页面。所以,一个非常典型的设置。
REST API 提供的数据是 "untrusted",因为它从数据库中获取用户提供的内容。因此,例如,它可能会获取如下内容:
{
"message": "<script>alert("Gotcha!")</script>"
}
显然,如果我的客户端代码将其直接呈现到页面的 DOM 中,那么我已经创建了一个 XSS 漏洞。所以,这个内容需要先HTML转义。
问题是,当输出不可信的内容时,我应该在服务器端对内容进行转义,还是在客户端进行转义?即,我的 API return 应该是原始内容,然后让客户端 Javascript 代码负责转义特殊字符,还是我的 API return "safe"内容:
{
"message": "<script>alert('Gotcha!');<\/script>"
}
那个已经转义了?
一方面,客户端似乎不必担心来自我的服务器的不安全数据。另一方面,有人可能会争辩说,当我们确切地如何 知道数据将被使用时,应该总是在最后一分钟转义输出。
哪种方法是正确的?
注意:关于处理输入有很多问题,是的,我知道客户端代码总是可以被操纵的。这个问题是关于输出数据来自我的服务器可能不可信。
更新:我调查了其他人在做什么,似乎有些 REST API 倾向于发送 "unsafe" JSON. Gitter 的 API 实际上两者都发送了,这是一个有趣的想法:
[
{
"id":"560ab5d0081f3a9c044d709e",
"text":"testing the API: <script>alert('hey')</script>",
"html":"testing the API: <script>alert('hey')</script>",
"sent":"2015-09-29T16:01:19.999Z",
"fromUser":{
...
},"unread":false,
"readBy":0,
"urls":[],
"mentions":[],
"issues":[],
"meta":[],
"v":1
}
]
请注意,他们在 text
键中发送原始内容,然后在 html
键中发送 HTML 转义版本。不错的主意,IMO。
我已经接受了一个答案,但我不认为这是一个简单的问题。我想鼓励就此主题进行进一步讨论。
对于输出转义:
我建议阅读这篇 XSS Filter Evasion Cheat Sheet。
为了正确地防止用户,你最好不仅逃逸,而且在逃逸之前用适当的反 XSS 库过滤它。喜欢 htmLawed, or HTML Purifier, or any from this thread.
恕我直言 sanitizing 每当您要在 Web 项目中显示用户输入的数据时,都应该对其进行处理。
should I escape the content on the server side, or the client side? I.e., should my API return the raw content, and then make it the client Javascript code's responsibility to escape the special characters, or should my API return "safe" content:
最好return已经转义,xss净化内容,所以:
- 如果来自服务器上的 xss,则获取原始数据并进行净化
- 逃脱
- Return 到 JavaScript
此外,您还应该注意一件重要的事情,例如您网站的负载和 read/write 余额:例如,如果您的客户输入一次数据,而您要向 100 万用户显示这些数据,那么您更喜欢:运行 每次读取一百万次(输出保护)写入前一次保护逻辑(输入保护)?
如果您要在一个页面上显示大约 1000 篇帖子并在客户端转义每个帖子,它在客户端移动设备上的效果如何 phone?最后一个将帮助您选择在客户端或服务器上保护数据的位置。
这个答案更侧重于争论是否进行客户端转义还是服务器端转义,因为 OP 似乎知道反对输入转义还是输出转义的论点。
为什么不对客户端进行转义?
我认为在 javascript 级别转义不是一个好主意。我脑海中浮现的一个问题是,如果清理脚本中存在错误,它不会 运行,然后危险的脚本将被允许 运行。所以你引入了一个向量,攻击者可以在其中尝试制作输入来破坏 JS 清理器,这样他们的纯脚本就可以 运行。我也不知道 JS 中有 运行 的任何内置 AntiXSS 库。我确信有人已经制作了一个,或者可以制作一个,但是已经建立的服务器端示例更值得信赖。还值得一提的是,用 JS 编写适用于所有浏览器的消毒程序并非易事。
好的,如果你同时逃脱怎么办?
转义服务器端和客户端让我有点困惑,不应该提供任何额外的安全性。你提到了双重转义的困难,我也经历过那种痛苦。
为什么服务器端足够好?
转义服务器端应该就足够了。你关于尽可能晚地做这件事的观点是有道理的,但我认为逃避客户端的缺点被你这样做可能获得的任何微小好处所抵消。威胁在哪里?如果攻击者存在于您的站点和客户端之间,则客户端已经受到威胁,因为他们可以根据需要发送带有脚本的空白 html 文件。您需要尽最大努力发送一些安全的东西,而不仅仅是发送处理您的危险数据的工具。
仅在客户端转义.
客户端逃逸的原因是安全:服务器的输出是客户端的输入,客户端不应该信任它。如果您假设输入已经被转义,那么您可能会通过恶意反向代理等方式向客户端发起攻击。这与您应该始终在服务器端验证输入的原因并无太大区别,即使您还包括客户端验证。
不在服务器端转义的原因是关注点分离:服务器不应该假定客户端打算将数据呈现为HTML。服务器的输出应尽可能与媒体无关(当然,考虑到 JSON 和数据结构的限制),以便客户端可以最轻松地将其转换为所需的任何格式。
TLDR; 如果你的 API 是传达格式化信息,它应该输出 HTML 编码的字符串。 警告:任何消费者都需要相信您的API不会输出恶意代码。内容安全策略也可以帮助解决这个问题。
如果你的API只输出纯文本,那么HTML在客户端编码(因为<
在纯文本中也意味着<
任何输出)。
时间不长,还没看完:
如果您同时拥有 API 和 Web 应用程序,那么任何一种方式都是可以接受的。只要您不输出 JSON 到 HTML 没有十六进制实体编码的页面 like this:
<%
payload = "[{ foo: '" + foo + "'}]"
%>
<script><%= payload %></script>
那么不管你服务器上的代码&
变成&
还是浏览器里的代码&
变成&
都没有关系。
让我们以你的问题为例:
[
{
"id":"560ab5d0081f3a9c044d709e",
"text":"testing the API: <script>alert('hey')</script>",
"html":"testing the API: <script>alert('hey')</script>",
"sent":"2015-09-29T16:01:19.999Z",
如果上面的代码是 return 从 api.example.com 编辑的,你从 www.example.com 调用它,当你控制双方时,你可以决定是否要采用纯文本," text
”,或格式化文本,“html
”。
重要的是要记住,插入 html
的任何变量都已在服务器端进行了 HTML 编码。并且还假设已执行正确的 JSON 编码,这会阻止任何引号字符被破坏或更改 JSON 的上下文(为简单起见,上面未显示)。
text
将使用 Node.textContent
and html
as Element.innerHTML
插入到文档中。使用 Node.textContent
将导致浏览器忽略可能存在的任何 HTML 格式和脚本,因为像 <
这样的字符在字面上被视为在页面上输出为 <
。
请注意,您的示例显示用户内容作为脚本输入。即用户在您的应用程序中输入 <script>alert('hey')</script>
,它 不是 API 生成的。如果您的 API 实际上想将输出标签作为其功能的一部分,那么它必须将它们放在 JSON:
"html":"<u>Underlined</u>"
然后您的 text
将不得不 仅输出不带格式的文本:
"text":"Underlined"
因此,您的 API 在向 Web 应用程序消费者发送信息时不再传输富文本,仅传输纯文本。
但是,如果第三方正在使用您的 API,那么他们可能希望以纯文本形式从您的 API 获取数据,因为这样他们就可以设置 Node.textContent
(或 HTML 编码)在客户端自己,知道它是安全的。如果您 return HTML 那么您的消费者需要相信您 HTML 不包含任何恶意脚本。
因此,如果上述内容来自 api.example.com,但您的消费者是第三方网站,例如 www.example.edu,那么他们可能会更愿意接受 text
而不是HTML。在这种情况下,您的输出可能需要更精细地定义,而不是输出
"text":"Thank you Alice for signing up."
你会输出
[{ "name", "alice",
"messageType": "thank_you" }]
或类似的,所以您不再在 JSON 中定义布局,您只是传达信息供客户端使用自己的样式进行解释和格式化。为了进一步阐明我的意思,如果您所有的消费者得到的是
"text":"Thank you Alice for signing up."
并且他们想以粗体显示名称,如果不进行复杂的解析,他们将很难做到这一点。然而,通过在粒度级别定义 API 输出,消费者可以获取相关的输出片段,如变量,然后应用他们自己的 HTML 格式,而不必相信你的 API只输出粗体标签(<b>
)而不输出恶意的 JavaScript(无论是来自用户还是来自你,如果你确实是恶意的,或者如果你的 API 已经被破坏)。
我有一个应用程序,它包含一个用 PHP 编写的服务器端 REST API,以及一些使用此 API 并使用的客户端 Javascript它生成的 JSON 用于呈现页面。所以,一个非常典型的设置。
REST API 提供的数据是 "untrusted",因为它从数据库中获取用户提供的内容。因此,例如,它可能会获取如下内容:
{
"message": "<script>alert("Gotcha!")</script>"
}
显然,如果我的客户端代码将其直接呈现到页面的 DOM 中,那么我已经创建了一个 XSS 漏洞。所以,这个内容需要先HTML转义。
问题是,当输出不可信的内容时,我应该在服务器端对内容进行转义,还是在客户端进行转义?即,我的 API return 应该是原始内容,然后让客户端 Javascript 代码负责转义特殊字符,还是我的 API return "safe"内容:
{
"message": "<script>alert('Gotcha!');<\/script>"
}
那个已经转义了?
一方面,客户端似乎不必担心来自我的服务器的不安全数据。另一方面,有人可能会争辩说,当我们确切地如何 知道数据将被使用时,应该总是在最后一分钟转义输出。
哪种方法是正确的?
注意:关于处理输入有很多问题,是的,我知道客户端代码总是可以被操纵的。这个问题是关于输出数据来自我的服务器可能不可信。
更新:我调查了其他人在做什么,似乎有些 REST API 倾向于发送 "unsafe" JSON. Gitter 的 API 实际上两者都发送了,这是一个有趣的想法:
[
{
"id":"560ab5d0081f3a9c044d709e",
"text":"testing the API: <script>alert('hey')</script>",
"html":"testing the API: <script>alert('hey')</script>",
"sent":"2015-09-29T16:01:19.999Z",
"fromUser":{
...
},"unread":false,
"readBy":0,
"urls":[],
"mentions":[],
"issues":[],
"meta":[],
"v":1
}
]
请注意,他们在 text
键中发送原始内容,然后在 html
键中发送 HTML 转义版本。不错的主意,IMO。
我已经接受了一个答案,但我不认为这是一个简单的问题。我想鼓励就此主题进行进一步讨论。
对于输出转义:
我建议阅读这篇 XSS Filter Evasion Cheat Sheet。
为了正确地防止用户,你最好不仅逃逸,而且在逃逸之前用适当的反 XSS 库过滤它。喜欢 htmLawed, or HTML Purifier, or any from this thread.
恕我直言 sanitizing 每当您要在 Web 项目中显示用户输入的数据时,都应该对其进行处理。
should I escape the content on the server side, or the client side? I.e., should my API return the raw content, and then make it the client Javascript code's responsibility to escape the special characters, or should my API return "safe" content:
最好return已经转义,xss净化内容,所以:
- 如果来自服务器上的 xss,则获取原始数据并进行净化
- 逃脱
- Return 到 JavaScript
此外,您还应该注意一件重要的事情,例如您网站的负载和 read/write 余额:例如,如果您的客户输入一次数据,而您要向 100 万用户显示这些数据,那么您更喜欢:运行 每次读取一百万次(输出保护)写入前一次保护逻辑(输入保护)?
如果您要在一个页面上显示大约 1000 篇帖子并在客户端转义每个帖子,它在客户端移动设备上的效果如何 phone?最后一个将帮助您选择在客户端或服务器上保护数据的位置。
这个答案更侧重于争论是否进行客户端转义还是服务器端转义,因为 OP 似乎知道反对输入转义还是输出转义的论点。
为什么不对客户端进行转义?
我认为在 javascript 级别转义不是一个好主意。我脑海中浮现的一个问题是,如果清理脚本中存在错误,它不会 运行,然后危险的脚本将被允许 运行。所以你引入了一个向量,攻击者可以在其中尝试制作输入来破坏 JS 清理器,这样他们的纯脚本就可以 运行。我也不知道 JS 中有 运行 的任何内置 AntiXSS 库。我确信有人已经制作了一个,或者可以制作一个,但是已经建立的服务器端示例更值得信赖。还值得一提的是,用 JS 编写适用于所有浏览器的消毒程序并非易事。
好的,如果你同时逃脱怎么办?
转义服务器端和客户端让我有点困惑,不应该提供任何额外的安全性。你提到了双重转义的困难,我也经历过那种痛苦。
为什么服务器端足够好?
转义服务器端应该就足够了。你关于尽可能晚地做这件事的观点是有道理的,但我认为逃避客户端的缺点被你这样做可能获得的任何微小好处所抵消。威胁在哪里?如果攻击者存在于您的站点和客户端之间,则客户端已经受到威胁,因为他们可以根据需要发送带有脚本的空白 html 文件。您需要尽最大努力发送一些安全的东西,而不仅仅是发送处理您的危险数据的工具。
仅在客户端转义.
客户端逃逸的原因是安全:服务器的输出是客户端的输入,客户端不应该信任它。如果您假设输入已经被转义,那么您可能会通过恶意反向代理等方式向客户端发起攻击。这与您应该始终在服务器端验证输入的原因并无太大区别,即使您还包括客户端验证。
不在服务器端转义的原因是关注点分离:服务器不应该假定客户端打算将数据呈现为HTML。服务器的输出应尽可能与媒体无关(当然,考虑到 JSON 和数据结构的限制),以便客户端可以最轻松地将其转换为所需的任何格式。
TLDR; 如果你的 API 是传达格式化信息,它应该输出 HTML 编码的字符串。 警告:任何消费者都需要相信您的API不会输出恶意代码。内容安全策略也可以帮助解决这个问题。
如果你的API只输出纯文本,那么HTML在客户端编码(因为<
在纯文本中也意味着<
任何输出)。
时间不长,还没看完:
如果您同时拥有 API 和 Web 应用程序,那么任何一种方式都是可以接受的。只要您不输出 JSON 到 HTML 没有十六进制实体编码的页面 like this:
<%
payload = "[{ foo: '" + foo + "'}]"
%>
<script><%= payload %></script>
那么不管你服务器上的代码&
变成&
还是浏览器里的代码&
变成&
都没有关系。
让我们以你的问题为例:
[
{
"id":"560ab5d0081f3a9c044d709e",
"text":"testing the API: <script>alert('hey')</script>",
"html":"testing the API: <script>alert('hey')</script>",
"sent":"2015-09-29T16:01:19.999Z",
如果上面的代码是 return 从 api.example.com 编辑的,你从 www.example.com 调用它,当你控制双方时,你可以决定是否要采用纯文本," text
”,或格式化文本,“html
”。
重要的是要记住,插入 html
的任何变量都已在服务器端进行了 HTML 编码。并且还假设已执行正确的 JSON 编码,这会阻止任何引号字符被破坏或更改 JSON 的上下文(为简单起见,上面未显示)。
text
将使用 Node.textContent
and html
as Element.innerHTML
插入到文档中。使用 Node.textContent
将导致浏览器忽略可能存在的任何 HTML 格式和脚本,因为像 <
这样的字符在字面上被视为在页面上输出为 <
。
请注意,您的示例显示用户内容作为脚本输入。即用户在您的应用程序中输入 <script>alert('hey')</script>
,它 不是 API 生成的。如果您的 API 实际上想将输出标签作为其功能的一部分,那么它必须将它们放在 JSON:
"html":"<u>Underlined</u>"
然后您的 text
将不得不 仅输出不带格式的文本:
"text":"Underlined"
因此,您的 API 在向 Web 应用程序消费者发送信息时不再传输富文本,仅传输纯文本。
但是,如果第三方正在使用您的 API,那么他们可能希望以纯文本形式从您的 API 获取数据,因为这样他们就可以设置 Node.textContent
(或 HTML 编码)在客户端自己,知道它是安全的。如果您 return HTML 那么您的消费者需要相信您 HTML 不包含任何恶意脚本。
因此,如果上述内容来自 api.example.com,但您的消费者是第三方网站,例如 www.example.edu,那么他们可能会更愿意接受 text
而不是HTML。在这种情况下,您的输出可能需要更精细地定义,而不是输出
"text":"Thank you Alice for signing up."
你会输出
[{ "name", "alice",
"messageType": "thank_you" }]
或类似的,所以您不再在 JSON 中定义布局,您只是传达信息供客户端使用自己的样式进行解释和格式化。为了进一步阐明我的意思,如果您所有的消费者得到的是
"text":"Thank you Alice for signing up."
并且他们想以粗体显示名称,如果不进行复杂的解析,他们将很难做到这一点。然而,通过在粒度级别定义 API 输出,消费者可以获取相关的输出片段,如变量,然后应用他们自己的 HTML 格式,而不必相信你的 API只输出粗体标签(<b>
)而不输出恶意的 JavaScript(无论是来自用户还是来自你,如果你确实是恶意的,或者如果你的 API 已经被破坏)。