簡體   English   中英

React:使用危險的SetInnerHTML插入時腳本標簽不起作用

[英]React: Script tag not working when inserted using dangerouslySetInnerHTML

我正在嘗試使用 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,但無法調用該腳本標簽中定義的函數。 如果這不是正確的方法,是否還有其他方法可以注入腳本標記的內容。

我創建了一個 React 組件,它的工作原理非常類似於dangerouslySetInnerHtml的SetInnerHtml,但另外它會執行它在 html 字符串上找到的所有 js 代碼,檢查一下,它可能對你有幫助:

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

這是完成它的一種骯臟的方式,對這里發生的事情的一些解釋,您通過正則表達式提取腳本內容,並僅使用 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')
);

我認為您不需要在此處使用連接 ( + )。

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 SetInnerHTML。

但是讓我們回到這個問題。 您不需要使用正則表達式來訪問腳本標簽的內容。 如果您添加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);"/>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM