Search code examples
reactjsbrowserxsscontent-security-policydangerouslysetinnerhtml

dangerouslySetInnerHTML not working with a <script>


I have the following code:

const Show = () => {
  const dangerousMarkup = { __html: "<script>alert('ERROR');</script>" };
  return (
             div dangerouslySetInnerHTML={dangerousMarkup} />
            <div>
              <div>
                The length of dangerousMarkup is: {dangerousMarkup.__html.length}
                <br />
                The instructions are: {dangerousMarkup.__html }
              </div>
            </div>
    )

The alert is not showing up but the text is. I will see the following on my screen:

The length of dangerousMarkup is: 32
The instructions are: <script>alert('ERROR');</script>

I have tested this on Firefox, Chrome, and Edge, all with the same results. I need to be able to have the XSS working.

Before anybody says, "This is dangerous" or "don't do this" this is for a lab exercise I'm putting together to teach students how to detect an XSS exposure. This code will only run in a controlled lab environment.

I have attempted to run this code with const dangerousMarkup = { __html: "<p>some raw html</p>" }; as the html and this does work as expected with the "some raw html" appearing in the browser.

I have also looked at the documentation.

I installed Disable Content Security Policy on my Chrome browser from the Chrome store.

BTW, this is the much simplified case of the problem as the actual code is reading the script which a user has input on another screen and then saved to a postgresql database.


Solution

  • I found an answer to my own question and I am posting it here for those who come behind me.

    The short answer is modern browsers are now disabling scripts which originate from the on-line user. No amount of modifying the Content Security Policies will change this. Even disabling the CSP will not allow a user to input a script to run.Therefore, a strictly React, Vite solution is not possible.

    But there is a work around. You can create a pure HTML file and place it in the public directory. Then have your React component call the HTML file with parameters. For my application, I placed the HTML file within a frame. The HTML file has to run code specifically for the tag and use eval(script) in order to get the script to run.

    I remind you this is extremely dangerous!

    Code from within the react component which creates the frame:

    <iframe
          src={`/ShowSpecial.html?content=${encodeURIComponent(
            Instructions  
          )}`}
          width="100%"
          height="500px"
          title="Show Special"
        ></iframe>
    

    Here is the HTML file:

    <!-- public/ShowSpecial.html -->
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Show Special</title>
      </head>
      <body>
        <div id="special-content">Loading...</div>
    
        <script>
          // Function to get URL parameters
          function getUrlParameter(name) {
            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
            var results = regex.exec(location.search);
            return results === null
              ? ""
              : decodeURIComponent(results[1].replace(/\+/g, " "));
          }
    
          // Get the special content from URL parameter
          var specialContent = getUrlParameter("content");
    
          // Display the special content (dangerous)
          document.getElementById("special-content").innerHTML = specialContent;
    
          // Execute the script (dangerous)
          if (specialContent.includes("<script>")) {
            specialContent = specialContent.replace(/<\/?script[^>]*>/gi, '');
            // Execute the script (dangerous)
            try {
              eval(specialContent);
            } catch (e) {
              console.error("Error executing script:", e);
            }
          }
        </script>
      </body>
    </html>