OPTIMUS PRIME Asked:2020-06-19 07:39:48 +0000 UTC2020-06-19 07:39:48 +0000 UTC 2020-06-19 07:39:48 +0000 UTC XSS 攻击:应该使用什么来代替 innerHTML / insertAdjacentHTML? 772 在什么情况下可以安全地将 HTML 添加为一行,什么时候应该避免这种方法?然后用什么来代替它们? javascript 2 个回答 Voted Best Answer Qwertiy 2020-06-19T08:52:18Z2020-06-19T08:52:18Z 从历史上看,模板引擎一直用于处理标记,向其中添加一些数据,然后通过 innerHTML 将其插入。这是因为浏览器解析 html 比从 js 创建元素更快。我不能确定当前浏览器的处理速度,但现代框架(例如 React 和 Angular)使用createElement. 好吧,如果您考虑一下-应该更快-解析标记,然后创建元素,还是立即创建元素?如果显而易见的答案被证明是错误的,那么这是浏览器优化的明确领域。 无论如何,首先,值得考虑的不是工作的速度,而是正确性。 使用标记时要考虑的事项: 该数据是否应该包含标记? 数据是可信的或可以从用户那里获取(包括通过 url、从外部来源等) 更改标记将如何影响页面在脚本方面的正确性? 我们确定解析标记会产生类似的 dom 树吗? 预期加价? 在大多数情况下,这个问题的答案是否定的。 如果文本显示为标记,会发生什么情况?用通常的词 - 没有。但是如果突然遇到特殊字符,它们就会消失。例如,我们想显示不等式a<b,但浏览器会吃掉<b标签的开头,结果会不正确。 Предположим, что a<b, тогда ... 即使是中性文本也是如此,其目的不是为了损害网站。 用户数据 用户输入的内容不应该在没有额外处理的情况下变成 html 标记。有两个地方不应该这样做 - 编写页面代码时在服务器上,以及在将数据插入标记时在客户端上。 在服务器的情况下,注入的范围很大——您可以简单地编写<script>alert(1)</script>或关闭几个额外的标签并破坏整个页面的标记,或者尝试注释掉页面的一部分,或者简单地将链接定位到钓鱼网站使用 css 通向主网站的标志。 如果数据是通过脚本插入到 中的innerHTML,那么标记就不能超出对应元素的范围(但是<style>,没有人取消标记),脚本的放置就有点困难了:<img src="/no" onerror="alert(1)">. 但基本上所有相同的攻击仍然是可能的。 document.querySelector('main').innerHTML = '<img src="/no" onerror="console.log(1)">' <main></main> 破坏脚本? 当我们通过 更改标记时会发生什么innerHTML?所有新的标记都被重新解析,新的html 元素被创建——即使是没有改变的部分。显而易见,这是低效的,但存在更大的问题。如果脚本将处理程序挂在某些元素上,那么在更新标记后,处理程序将继续挂在不再位于 dom 树中的旧元素上。因此,在添加标记时,您应该选择insertAdjacentHTML,而不是innerHTML +=: document.getElementById('ih').addEventListener('click', e => { e.target.parentElement.innerHTML += "<i></i>" }) document.getElementById('ia').addEventListener('click', e => { e.target.parentElement.insertAdjacentHTML('beforeend', "<i></i>") }) document.getElementById('ac').addEventListener('click', e => { e.target.parentElement.appendChild(document.createElement('i')) }) i { display: inline-block; background: silver; height: 1em; width: 1em; border-radius: 50%; margin-left: 4px; vertical-align: middle; } <p><button id="ih">innerHTML</button></p> <p><button id="ia">insertAdjacentHTML</button></p> <p><button id="ac">appendChild</button></p> 破坏标记? 任何使用innerHTMLdom-element 或 dom-element 的操作都不会超出此元素。但是,如果我们设置了一些棘手的东西,结果树是无效的,那么在重新解析之后,我们会对结果感到非常惊讶: var oldP = document.querySelector('main p') var newP = document.createElement('p') newP.textContent = "456" oldP.appendChild(newP) document.querySelector('button').addEventListener('click', e => { var main = document.querySelector('main') console.log(main.innerHTML) main.innerHTML = main.innerHTML console.log(main.innerHTML) }) p p { color: blue; } p + p { color: red; } <main><p>123</p></main> <button>Ooops!</button> 什么时候使用标记? 我们知道我们正在添加标记,并且来自受信任的来源。 如果有理由将数据插入到标记中,那么应该适当地对其进行转义以确保它仍然是一个字符串并且不会成为带有元素的标记。 如果我们想为元素添加标记,我们应该更喜欢添加标记而不是完全覆盖。 textContent 和 innerText 该属性textContent使向元素中插入任意文本变得容易,并且浏览器负责转义。如果我们只需要插入文本,无论用户在那里写什么,这是理想的: document.querySelector('main').textContent = '<img src="/no" onerror="console.log(1)">' <main></main> 至于属性innerText,它几乎不应该被使用。写入时,它的行为方式与 相同textContent,但在某些情况下慢十倍。阅读时,它不会返回整个文本,而只返回可见的文本 - 如果需要,可以使用它,但这很少需要。无论如何,它都包含在标准中。 var main = document.querySelector('main') console.log(main.textContent) console.log(main.innerText) <main> <style>p { color: blue; }</style> <p>123</p> <p hidden>123</p> И немного текста </main> 何时使用元素? 在我看来,几乎总是如此,除非在极少数情况下需要使用标记。浏览器本身会在使用时负责转义textContent,并且在创建和插入元素时,它们会准确地插入到我们想要的位置,并且不会破坏周围的内容。在错误的地方意外关闭标签几乎是不可能的。 什么是几乎可以肯定的错误? 将用户或不受信任的数据放入标记中。 从那里读到textContent然后写到innerHTML——这里的规则很简单——从他们读到的地方,他们把它写下来。如果任何方向的传输被证明是副作用(而不是有意理解的行为),那么它至少会破坏数据的显示,并且最多会在站点上造成漏洞。通常,必须小心地对作为字符串的标记进行所有操作。 +=上使用innerHTML。 过于频繁地使用文档中的 dom 元素也是不可取的。在完成处理后将元素插入到文档中可以显着提高性能。如果你需要插入一组元素,那么你可以使用文档片段。 我只需要完成我的页面 碰巧代码不是为站点编写的,而是为了将一个完全陌生的页面带到可打印的表单中。哪些规则应该遵守,哪些不应该? 这在性能和脚本破坏方面可能更容易,但在标记中包装文本仍可能会在意外位置破坏数据,因此仍应避免。来自标签的脚本<script>不会被执行,但内联事件处理程序有时会令人惊讶,尽管它们通常不存在于现代网站上。 Жека Диулин 2020-06-19T08:35:10Z2020-06-19T08:35:10Z 能: 使用来自管理员和开发人员创建的本机或受信任服务器的 HTML; 在服务器上根据用户数据构建的 HTML。 这是被禁止的: 在客户端构造HTML内容,然后发送到服务器进行存储; 跳过检查用户数据的 HTML 标记。 用户创建 HTML 内容的方案是这样的: 用户看到内容构造器(就像 StackOverflow 上的这里)并填写必填字段,使用可用的工具; 表单提交数据而不生成任何标签。例如,您可以使用 JSON { title: "", body: "" ... }: 服务器检查并清理接收到的数据。任何(或只有危险的)HTML 标签都应该被丢弃,没有标签的脚本不再看起来像脚本,而是像文本; 服务器构造一个 HTML 字符串并将其存储在数据库中。 因此,通过良好的验证和服务器端构造函数,自定义 HTML 也是值得信赖的。
从历史上看,模板引擎一直用于处理标记,向其中添加一些数据,然后通过 innerHTML 将其插入。这是因为浏览器解析 html 比从 js 创建元素更快。我不能确定当前浏览器的处理速度,但现代框架(例如 React 和 Angular)使用
createElement. 好吧,如果您考虑一下-应该更快-解析标记,然后创建元素,还是立即创建元素?如果显而易见的答案被证明是错误的,那么这是浏览器优化的明确领域。无论如何,首先,值得考虑的不是工作的速度,而是正确性。
使用标记时要考虑的事项:
预期加价?
在大多数情况下,这个问题的答案是否定的。
如果文本显示为标记,会发生什么情况?用通常的词 - 没有。但是如果突然遇到特殊字符,它们就会消失。例如,我们想显示不等式
a<b,但浏览器会吃掉<b标签的开头,结果会不正确。即使是中性文本也是如此,其目的不是为了损害网站。
用户数据
用户输入的内容不应该在没有额外处理的情况下变成 html 标记。有两个地方不应该这样做 - 编写页面代码时在服务器上,以及在将数据插入标记时在客户端上。
在服务器的情况下,注入的范围很大——您可以简单地编写
<script>alert(1)</script>或关闭几个额外的标签并破坏整个页面的标记,或者尝试注释掉页面的一部分,或者简单地将链接定位到钓鱼网站使用 css 通向主网站的标志。如果数据是通过脚本插入到 中的
innerHTML,那么标记就不能超出对应元素的范围(但是<style>,没有人取消标记),脚本的放置就有点困难了:<img src="/no" onerror="alert(1)">. 但基本上所有相同的攻击仍然是可能的。破坏脚本?
当我们通过 更改标记时会发生什么
innerHTML?所有新的标记都被重新解析,新的html 元素被创建——即使是没有改变的部分。显而易见,这是低效的,但存在更大的问题。如果脚本将处理程序挂在某些元素上,那么在更新标记后,处理程序将继续挂在不再位于 dom 树中的旧元素上。因此,在添加标记时,您应该选择insertAdjacentHTML,而不是innerHTML +=:破坏标记?
任何使用
innerHTMLdom-element 或 dom-element 的操作都不会超出此元素。但是,如果我们设置了一些棘手的东西,结果树是无效的,那么在重新解析之后,我们会对结果感到非常惊讶:什么时候使用标记?
textContent 和 innerText
该属性
textContent使向元素中插入任意文本变得容易,并且浏览器负责转义。如果我们只需要插入文本,无论用户在那里写什么,这是理想的:至于属性
innerText,它几乎不应该被使用。写入时,它的行为方式与 相同textContent,但在某些情况下慢十倍。阅读时,它不会返回整个文本,而只返回可见的文本 - 如果需要,可以使用它,但这很少需要。无论如何,它都包含在标准中。何时使用元素?
在我看来,几乎总是如此,除非在极少数情况下需要使用标记。浏览器本身会在使用时负责转义
textContent,并且在创建和插入元素时,它们会准确地插入到我们想要的位置,并且不会破坏周围的内容。在错误的地方意外关闭标签几乎是不可能的。什么是几乎可以肯定的错误?
textContent然后写到innerHTML——这里的规则很简单——从他们读到的地方,他们把它写下来。如果任何方向的传输被证明是副作用(而不是有意理解的行为),那么它至少会破坏数据的显示,并且最多会在站点上造成漏洞。通常,必须小心地对作为字符串的标记进行所有操作。+=上使用innerHTML。过于频繁地使用文档中的 dom 元素也是不可取的。在完成处理后将元素插入到文档中可以显着提高性能。如果你需要插入一组元素,那么你可以使用文档片段。
我只需要完成我的页面
碰巧代码不是为站点编写的,而是为了将一个完全陌生的页面带到可打印的表单中。哪些规则应该遵守,哪些不应该?
这在性能和脚本破坏方面可能更容易,但在标记中包装文本仍可能会在意外位置破坏数据,因此仍应避免。来自标签的脚本
<script>不会被执行,但内联事件处理程序有时会令人惊讶,尽管它们通常不存在于现代网站上。能:
这是被禁止的:
用户创建 HTML 内容的方案是这样的:
{ title: "", body: "" ... }:因此,通过良好的验证和服务器端构造函数,自定义 HTML 也是值得信赖的。