使用危险的SetInnerHTML 执行脚本

Posted

技术标签:

【中文标题】使用危险的SetInnerHTML 执行脚本【英文标题】:Script execution with dangerouslySetInnerHTML 【发布时间】:2021-01-19 21:11:16 【问题描述】:

我在几个地方读到过,当我们使用 React dangerouslySetInnerhtml 插入 html 片段时,脚本没有被执行。

但是我只是尝试插入这个:

<img src= "img.png" onload="alert('picture loaded')" >  

警报被触发了。

这并不让我感到惊讶(这就是我首先进行测试的原因),但我想更好地理解“脚本不执行”的含义。

我的问题:

除了我上面的例子,还有其他类型的脚本 会执行吗? 有没有办法完全阻止脚本执行, 包括嵌入在我的示例中的 html 事件处理程序中的那些? 如果脚本标签定义了一个函数,该函数是否仍然存在? 已加载并可稍后调用? 还有其他我应该注意的行为吗?

[编辑] 我在一个函数组件中使用dangerouslySetInnerHTML

const htmlString = '<img src="img.png" onload="alert('picture loaded')" >'

在返回语句(JSX)中:

return <div dangerouslySetInnerHTML=__html: htmlString />

【问题讨论】:

你是如何插入这个的? 我在帖子中添加了更多信息。希望答案不取决于使用 SetInnerHTML 的危险程度。 【参考方案1】:

“不会执行”的脚本是脚本标签,例如&lt;script&gt;。示例见这里:

const html = `
<script>console.log('this does NOT run');<\/script>
<img src onerror="console.log('but this will')">
`;
const App = () => 
  return <div dangerouslySetInnerHTML=__html: html />;
;
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>

内联处理程序,不是&lt;script&gt; 标签,可以运行,如果它们附加到的事件触发。 (上面,带有src 属性但没有有效路径的&lt;img&gt; 标记会引发错误,因此其onerror 内联处理程序会运行)

没有其他类别的脚本可以与dangerouslySetInnerHTML 结合运行(除非内联处理程序本身通过其他方式注入&lt;script&gt;,例如document.createElement('script'))。

有没有办法完全阻止脚本执行,包括嵌入在我的示例中的 html 事件处理程序中的那些?

您需要删除on- 属性。如果删除所有on- 属性,则可能触发的任何事件都不会导致意外脚本运行。您可以先通过 DOMParser 发送输入来净化输入:

const sanitize = (input) => 
  const doc = new DOMParser().parseFromString(input, 'text/html');
  for (const elm of doc.querySelectorAll('*')) 
    for (const attrib of elm.attributes) 
      if (attrib.name.startsWith('on')) 
        elm.removeAttribute(attrib.name);
      
    
  
  return doc.body.innerHTML;
;

const html = `
<div>
  <script>console.log('this does NOT run');<\/script>
  <img src onerror="console.log('but this will')">
  more content
  <style type="text/css"> img float: left; margin: 5px</style> 
</div>
`;
const App = () => 
  return <div dangerouslySetInnerHTML=__html: sanitize(html) />;
;
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>
<img src="https://www.gravatar.com/avatar/f117a950c689d3d6ec459885a908166e?s=32&d=identicon&r=PG">

如果脚本标签定义了一个函数,该函数是否仍会被加载并在以后调用?

不,因为&lt;script&gt; 标记根本不会运行,所以它内部发生的任何事情都不会做任何事情;内部定义的函数将不可见。要发生这样的事情,您必须以某种方式deliberately reload 新注入的&lt;script&gt; 标签,使其运行。

【讨论】:

快速提问,出于好奇:sanitize 方法也可以删除脚本吗? 你可以使用if (elm.tagName === 'SCRIPT') elm.remove(); 我的 IDE 对 for 循环不满意(说不是数组),我不得不用不同的方式编写它们。 由于某种原因,sanitize 函数也在我的测试中清除了一个内联样式元素。 它看起来适用于我,请参阅答案中对 sn-p 的编辑

以上是关于使用危险的SetInnerHTML 执行脚本的主要内容,如果未能解决你的问题,请参考以下文章

如何危险地使用SetInnerHTML

替代危险的SetInnerHTML

JSX 组件 + 危险的 SetInnerHTML?

在没有危险的SetInnerHTML的情况下在字符串上添加标记

在后台从 React 组件制作 HTML 字符串,如何在另一个 React 组件中通过危险的SetInnerHTML 使用该字符串

宝塔CentOS自动挂载教程(含解决提示:危险 数据盘为windwos分区,为了你的数据安全,请手动挂载,本脚本不执行任何操作)