ituy
ituy

Reputation: 241

Do `<script>` elements inserted through `.innerHTML` not get executed?

Is it specified that element.innerHTML = '<script>alert()</script>'; does execute the inserted script, or does behaviour vary between browsers? Can I rely on innerHTML not executing scripts?

Upvotes: 3

Views: 215

Answers (2)

dumbass
dumbass

Reputation: 27198

Yes, it is mandated by the WHATWG HTML standard that the innerHTML setter suppress the execution of scripts. This is specified in HTML LS 2024-11-26, §13.2.6.4.4 “The ‘in head’ insertion mode”, case “A start tag whose tag name is ‘script’”, point 4:

  1. If the parser was created as part of the HTML fragment parsing algorithm, then set the script element's already started to true. (fragment case)

Setting the “already started” flag will prevent execution. The HTML fragment parsing algorithm is used by the setter of .innerHTML and by .insertAdjacentHTML, from which it follows that script elements inserted through those means will be dormant:

document.body.innerHTML =
  '<script>console.log("this should never execute");<\/script>';

document.body.insertAdjacentHTML(
  'beforeend',
  '<script>console.log("still nothing");<\/script>'
);

Script nodes obtained from DOMParser will also be dormant, although for a different reason – a non-normative note helpfully explains (HTML LS, §8.5.1) what you would otherwise have to infer by digging through multiple algorithms in the specification:

Since document does not have a browsing context, scripting is disabled.

And you can see it for yourself:

const doc = new DOMParser().parseFromString(
  '<script>console.log("is this thing on?");<\/script>',
  'text/html');
document.body.appendChild(doc.querySelector('script'));

There is, however, a way to parse HTML code containing <script> tags into live elements that will trigger execution when inserted – it’s by invoking createContextualFragment on a DOM Range:

const frag = document.createRange().createContextualFragment(
  '<script>console.log("bingo!");<\/script>');
console.log("let's try this…");
document.body.appendChild(frag);

This is also standardized – although createContextualFragment also relies on the fragment parsing algorithm, which sets the “already started” flag, step 8 of that method’s algorithm (HTML LS, §8.5.7) undoes that:

  1. For each script of fragment node's script element descendants:
    1. Set script's already started to false.
    2. Set script's parser document to null.

Upvotes: 1

Billy Hudson
Billy Hudson

Reputation: 146

In HTML5 the behavior is specified to not execute inserted <script> elements, however as this page from Mozilla notes, there are still ways to get around this to execute JavaScript, so you can't rely on this for security.

HTML5 Spec: https://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0

script elements inserted using innerHTML do not execute when they are inserted.

https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#Security_considerations

there is still a security risk whenever you use innerHTML to set strings over which you have no control. For example:

const name = "<img src='x' onerror='alert(1)'>";
el.innerHTML = name; // shows the alert

For that reason, it is recommended that instead of innerHTML you use:

  • Element.SetHTML() to sanitize the text before it is inserted into the DOM.
  • Node.textContent when inserting plain text, as this inserts it as raw text rather than parsing it as HTML.

Upvotes: 5

Related Questions