使用危险地执行脚本SetInnerHTML

问题描述

我在很多地方都读到过,当我们使用React dangerouslySetInnerHTML插入html片段时,脚本不会执行。

但是我只是想插入这个:

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

警报已触发。

这并不令我感到意外(这就是为什么我首先进行测试的原因),但我想更好地理解“脚本不执行”的含义。

我的问题:

  • 除了上面的示例外,还有其他类型的脚本 将执行?
  • 有没有一种方法可以完全阻止脚本执行, 包括那些嵌入在HTML事件处理程序中的控件,例如我的示例?
  • 如果脚本标签定义了一个函数,该函数是否仍会 已加载并稍后可调用
  • 我应该知道的其他行为吗?

[编辑]我在功能组件中使用dangerouslySetInnerHTML

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

在return语句(JSX)中:

return <div dangerouslySetInnerHTML={{__html: htmlString}} />

解决方法

“将不执行”的脚本是脚本 tags ,例如<script>。参见此处的示例:

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>

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

没有其他类别的脚本可以与dangerouslySetInnerHTML结合运行(除非内联处理程序本身通过其他方式(例如<script>注入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">

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

否,因为<script>标记根本不会运行,因此它内部没有任何作用。内部定义的功能将不可见。为了使这种情况发生,您必须以某种方式deliberately reload新注入的<script>标记才能使其运行。