Executing script blocks in HTML injected via JavaScript

With all the challenges of XSS, most often your goal is to prevent unintentional script execution. Ironically, getting dynamically injected scripts to run when you want them to can be as hard as preventing those that you don’t want to.

My problem came up while building plugins for Docalytics documents. Essentially, we are allowing widgets between pages of an HTML5 document viewer. The owner of a document can define HTML and JavaScript to be placed between pages of the document to allow for things like video, surveys, etc.

The entire viewer is written in JavaScript, so these plugins were read by the viewer JS and created dynamically as needed. This meant that if the HTML created by the document owner included script tags, they should be run as needed.

Depending on your scenario, this might now be too hard. jQuery takes care of doing this for you when you add HTML via its methods, such as $(...).html(...). jQuery actually parses the HTML itself, identifies, script blocks, and executes them via eval(...). The problem comes in for scripts with an src attribute, rather than an inline script.

jQuery loads script tags with ansrc attribute via AJAX, and then executes them. This is fine if the script is located on your servers, but in my case the scripts were hosted on 3rd party sites, and the servers weren’t setup for cross-domain requests.

My final solution was based on this this StackOverflow question. I injected the HTML using the raw DOM APIs, then executed a helper function on that node to go back and execute the scripts. My modified version of the StackOverflow answer is below, which handles the case for src attributes on the scripts.

function executed_child_scripts_on_element ($element) {
        function nodeName(elem, name) {
            return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
        }

        function evalScript(elem) {
            var data = (elem.text || elem.textContent || elem.innerHTML || "" ),
                head = document.getElementsByTagName("head")[0] ||
                    document.documentElement,
                script = document.createElement("script");

            script.type = "text/javascript";

            if(elem.src) {
                script.src = elem.src;
                script.async = !!elem.async;
            } else {
                try {
                    // doesn't work on ie...
                    script.appendChild(document.createTextNode(data));
                } catch (e) {
                    // IE has funky script nodes
                    script.text = data;
                }
            }

            head.insertBefore(script, head.firstChild);
            head.removeChild(script);
        }

        // main section of function
        var scripts = [],
            script,
            children_nodes = $element[0].childNodes,
            child,
            i;

        for (i = 0; children_nodes[i]; i++) {
            child = children_nodes[i];
            if (nodeName(child, "script") &&
                (!child.type || child.type.toLowerCase() === "text/javascript")) {
                scripts.push(child);
            }
        }

        for (i = 0; scripts[i]; i++) {
            script = scripts[i];
            if (script.parentNode) {
                script.parentNode.removeChild(script);
            }
            evalScript(scripts[i]);
        }
    }

Leave a Comment


NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>