React:使用危险的SetInnerHTML插入时脚本标签不起作用

IT技术 javascript html reactjs script-tag
2021-03-10 19:23:21

我正在尝试使用 React 中的 risklySetInnerHTML 属性设置从我的服务器发送的 html 以显示在 div 内。我在里面也有脚本标签,并使用在该 html 中定义的函数。我在这里做了 JSFiddle 中的错误示例

这是测试代码:

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

var Hello = React.createClass({
  displayName: 'Hello',
  render: function() {
    return (<div dangerouslySetInnerHTML={{__html: x}} />);
  }
});

我检查了脚本标记已添加到 DOM,但无法调用该脚本标记中定义的函数。如果这不是正确的方法,是否还有其他方法可以注入脚本标记的内容。

5个回答

我创建了一个 React 组件,它的工作原理非常相似,dangerouslySetInnerHtml但另外它会执行它在 html 字符串上找到的所有 js 代码,检查一下,它可能对你有帮助:

https://www.npmjs.com/package/dangerously-set-html-content

是的,这是出于安全原因,但很高兴知道它可以帮助您!
2021-04-18 19:23:21
非常好!我不记得dangerouslySetInnerHTML没有使用<script>标签。您的组件运行良好。
2021-04-19 19:23:21
这应该是公认的答案,因为它是解决问题的最优雅的方法。
2021-04-26 19:23:21
很棒的工作克里斯托弗。这对我来说似乎是迄今为止最简单的解决方案。我正在使用typescript(并且不知道如何键入您的库),所以我最终只是窃取了您的代码并根据您的代码编写了自己的组件。这是 Christofer 所写组件的链接:github.com/christo-pr/dangerously-set-html-content/blob/master/...
2021-05-12 19:23:21
2021-05-13 19:23:21

这是完成它的一种肮脏的方式,对这里发生的事情的一些解释,您通过正则表达式提取脚本内容,并仅使用 react 呈现 html,然后在组件安装后脚本标记中的内容在全局范围内运行。

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

var extractscript=/<script>(.+)<\/script>/gi.exec(x);
x=x.replace(extractscript[0],"");

var Hello = React.createClass({
  displayName: 'Hello',
  componentDidMount: function() {
    // this runs the contents in script tag on a window/global scope
    window.eval(extractscript[1]);

  },
  render: function() {
    return (<div dangerouslySetInnerHTML={{__html: x}} />);
  }
});

ReactDOM.render(
  React.createElement(Hello),
  document.getElementById('container')
);
如果有多个脚本标签怎么办?
2021-04-27 19:23:21
对于多行脚本标记,reg 表达式可能不起作用,请尝试 var extractscript=/<script>[\s\S]*<\/script>/gi.exec(x);
2021-05-15 19:23:21

我认为您不需要在+这里使用连接 ( )。

var x = '<html><scr'+'ipt>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</scr'+'ipt><body><p onClick="pClicked()">Hello</p></body></html>';

我认为你可以这样做:

var x = '<html><script>alert("this.is.sparta");function pClicked() {console.log("p is clicked");}</script><body><p onClick="pClicked()">Hello</p></body></html>';

既然传给了dangerouslySetInnerHTML反正。

但是让我们回到这个问题。您不需要使用正则表达式来访问脚本标签的内容。id例如<script id="myId">...</script>如果添加属性,则可以轻松访问该元素。

让我们看一个这种实现的例子。

const x = `
  <html>
    <script id="myScript">
      alert("this.is.sparta");
      function pClicked() {console.log("p is clicked");}
    </script>
    <body>
      <p onClick="pClicked()">Hello</p>
    </body>
  </html>
`;

const Hello = React.createClass({

  displayName: 'Hello',

  componentDidMount() {
    const script = document.getElementById('myScript').innerHTML;
    window.eval(script);
  }

  render() {
    return <div dangerouslySetInnerHTML={{__html: x}} />;
  }

});

如果您有多个脚本,您可以添加一个数据属性[data-my-script],例如,然后使用 jQuery 访问它:

const x = `
  <html>
    <script data-my-script="">
      alert("this.is.sparta");
      function pClicked() {console.log("p is clicked");}
    </script>
    <script data-my-script="">
      alert("another script");
    </script>
    <body>
      <p onClick="pClicked()">Hello</p>
    </body>
  </html>
`;

const Hello = React.createClass({

  constructor(props) {
    super(props);

    this.helloElement = null;
  }

  displayName: 'Hello',

  componentDidMount() {
    $(this.helloElement).find('[data-my-script]').each(function forEachScript() {
      const script = $(this).text();
      window.eval(script);
    });
  }

  render() {
    return (
      <div
        ref={helloElement => (this.helloElement = helloElement)} 
        dangerouslySetInnerHTML={{__html: x}} 
      />
    );
  }

});

在任何情况下,避免使用 总是好的eval,因此另一种选择是获取文本并使用原始脚本内容附加一个新的脚本标记,而不是调用eval. 这个答案表明了这种方法

Dasith 对未来观点的回答的一点扩展......

我有一个非常相似的问题,但在我的情况下,我从服务器端获取了 HTML,这花了一段时间(报告解决方案的一部分,其中后端将报告呈现为 html)

所以我所做的非常相似,只是我处理了在 componentWillMount() 函数中运行的脚本:

import React from 'react';
import jsreport from 'jsreport-browser-client-dist'
import logo from './logo.svg';
import './App.css';

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            report: "",
            reportScript: ""
        }
    }

    componentWillMount() {
        jsreport.serverUrl = 'http://localhost:5488';
        let reportRequest = {template: {shortid: 'HJH11D83ce'}}
        // let temp = "this is temp"
        jsreport.renderAsync(reportRequest)
            .then(res => {
                let htmlResponse = res.toString()
                let extractedScript = /<script>[\s\S]*<\/script>/g.exec(htmlResponse)[0];
                // console.log('html is: ',htmlResponse)
                // console.log('script is: ',extractedScript)
                this.setState({report: htmlResponse})
                this.setState({reportScript: extractedScript})
            })
    }

    render() {
        let report = this.state.report
        return (
            <div className="App">
                <div className="App-header">
                    <img src={logo} className="App-logo" alt="logo"/>
                    <h2>Welcome to React</h2>
                </div>
                <div id="reportPlaceholder">
                    <div dangerouslySetInnerHTML={{__html: report}}/>

                </div>
            </div>
        );
    }

    componentDidUpdate() {
        // this runs the contents in script tag on a window/global scope
        let scriptToRun = this.state.reportScript
        if (scriptToRun !== undefined) {
            //remove <script> and </script> tags since eval expects only code without html tags
            let scriptLines = scriptToRun.split("\n")
            scriptLines.pop()
            scriptLines.shift()
            let cleanScript = scriptLines.join("\n")
            console.log('running script ',cleanScript)
            window.eval(cleanScript)
        }

    }
}

export default App;

希望这是有帮助的...

只需使用一些已知的 XSS 技巧即可。我们刚刚遇到一个案例,我们不得不注入一个脚本并且迫不及待地等待发布,所以我们的加载器如下所示:

<img src onerror="var script = document.createElement('script');script.src = 'http:';document.body.appendChild(script);"/>