How to Hook and Hide JavaScript APIs to Thwart XSS Attacks

This article explores practical techniques for intercepting and protecting JavaScript APIs—such as setAttribute—using MutationObserver, API hooks, random token naming, property hiding, and recursive iframe monitoring to build a resilient front‑end defense against XSS and other injection attacks.

Baidu Tech Salon
Baidu Tech Salon
Baidu Tech Salon
How to Hook and Hide JavaScript APIs to Thwart XSS Attacks

Overview

The author experiments with module interception on the web page, distinguishing static modules scanned by MutationObserver and dynamic modules intercepted via API hooks. The core idea of a hook is to execute custom code before the original function without modifying the original program, creating a chain‑call pattern that works from low‑level instruction interception up to aspect‑oriented programming.

Hooking setAttribute

A minimal hook is demonstrated by overriding Element.prototype.setAttribute. The original function is saved in raw_fn, the new implementation checks for suspicious script sources, prompts the user, and finally forwards the call.

// Save original
var raw_fn = Element.prototype.setAttribute;
// Override
Element.prototype.setAttribute = function(name, value) {
    if (this.tagName == 'SCRIPT' && /^src$/i.test(name)) {
        if (/xss/.test(value)) {
            if (confirm('Attempt to load suspicious module:

' + value + '

Block?')) {
                return;
            }
        }
    }
    raw_fn.apply(this, arguments);
};
// Test
var el = document.createElement('script');
el.setAttribute('SRC', 'http://www.etherdream.com/xss/alert.js');
document.body.appendChild(el);

Hook Leakage and Bypass

Exposing raw_fn as a global variable allows an attacker to bypass the hook by calling the original function directly. Even rewriting Function.prototype.apply can leak the saved reference, demonstrating that any overridden global method can become an attack surface.

Function.prototype.apply = function() {
    console.log('Got original interface:', this);
};
document.body.setAttribute('a', 1);

An image illustrating the leak:

Concealing the Original Interface

To avoid global exposure, the original method can be renamed with a unique token and stored as a non‑enumerable property using Object.defineProperty. Random tokens make static analysis harder.

(function() {
    var token = '$' + Math.random();
    Element.prototype[token] = Element.prototype.setAttribute;
    Element.prototype.setAttribute = function(name, value) {
        this[token](name, value);
    };
})();

Further hiding is achieved by setting enumerable: false on the token property, making it invisible to typical enumeration loops.

Object.defineProperty(Element.prototype, token, {
    value: Element.prototype.setAttribute,
    enumerable: false
});

Locking call and apply

Since many bypasses rely on call or apply, the article shows how to make these methods non‑writable and non‑configurable, effectively freezing them.

Object.defineProperty(Function.prototype, 'call', {
    value: Function.prototype.call,
    writable: false,
    configurable: false,
    enumerable: true
});
Object.defineProperty(Function.prototype, 'apply', {
    value: Function.prototype.apply,
    writable: false,
    configurable: false,
    enumerable: true
});

Reflecting Into New Frames

Attackers can create isolated iframe environments where the original hooks are absent. To protect these, the author installs a monitoring system that watches for newly inserted IFRAME elements using DOMNodeInserted (or MutationObserver) and injects the same hook into the iframe’s contentWindow.

function installHook(win) {
    var raw_fn = win.Element.prototype.setAttribute;
    win.Element.prototype.setAttribute = function(name, value) {
        alert(name);
        raw_fn.apply(this, arguments);
    };
    win.document.addEventListener('DOMNodeInserted', function(e) {
        if (e.target.tagName == 'IFRAME') {
            installHook(e.target.contentWindow);
        }
    }, true);
}
installHook(window);

This recursive installation ensures that even nested iframes receive the protective hook.

Limitations and Reverse Control

The article acknowledges that determined attackers can still bypass defenses by rewriting other native methods (e.g., RegExp.prototype.test, String.prototype.toUpperCase) or by injecting scripts via srcdoc or document.write. It suggests monitoring document.write after DOMContentLoaded and disabling rarely used properties like srcdoc when possible.

Conclusion

While the presented hooking techniques significantly raise the bar against XSS and similar attacks, the author warns that no single method is foolproof; continuous updates and layered defenses are required to keep up with evolving exploitation tactics.

frontendJavaScriptMutationObserversecurityXSSHookAPI interception
Baidu Tech Salon
Written by

Baidu Tech Salon

Baidu Tech Salon, organized by Baidu's Technology Management Department, is a monthly offline event that shares cutting‑edge tech trends from Baidu and the industry, providing a free platform for mid‑to‑senior engineers to exchange ideas.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.