Securely Bypassing X-Frame-Options or Content-Security-Policy in WebExtension

To embed third party content using iframe, the WebExtension may need to intercept HTTP response and modify headers but isn’t it bad in terms of security? That’s what I thought.

Bypassing X-Frame-Options

If your browser extension is using iframe to embed third party content where the source is dynamic, you have no option other than to bypass the X-Frame-Options header.

chrome.webRequest.onHeadersReceived.addListener(info => {
    const headers = info.responseHeaders; // original headers
    for (let i=headers.length-1; i>=0; --i) {
        let header = headers[i].name.toLowerCase();
        if (header === "x-frame-options" || header === "frame-options") {
            headers.splice(i, 1); // Remove the header
        }
    }
    // return modified headers
    return {responseHeaders: headers};
}, {
    urls: [ "<all_urls>" ], // match all pages
    types: [ "sub_frame" ] // for framing only
}, ["blocking", "responseHeaders"]);

This may be required when you are providing some functionality which relies on iframe. For me, the reason was a feature which allowed the user to embed third party content or web-app by its URL on the extension’s page.

But wait… what about the Content-Security-Policy header?

Content-Security-Policy header also has frame-ancestors directive which can be used to control if a page can be loaded in an iframe or not.

frame-ancestors directive can specify a list of allowed sources which can load the page in an iframe or prevent this for all parent origins.

Example CSP header

script-src https://script-source1.com http://script-source2.com; frame-ancestors 'self';

The above header is not allowing any third party parent origin to load this page in iframe.

Bypassing Content-Security-Policy

This header can be bypassed the same way as shown above.

chrome.webRequest.onHeadersReceived.addListener(info => {
    const headers = info.responseHeaders; // original headers
    for (let i=headers.length-1; i>=0; --i) {
        let header = headers[i].name.toLowerCase();
        if (header === "content-security-policy") { // csp header is found
            // modifying frame-ancestors; this implies that the directive is already present
            headers[i].value = headers[i].value.replace("frame-ancestors", "frame-ancestors https://yourpage.com/");
        }
    }
    // return modified headers
    return {responseHeaders: headers};
}, {
    urls: [ "<all_urls>" ], // match all pages
    types: [ "sub_frame" ] // for framing only
}, ["blocking", "responseHeaders"]);

Now your origin page is whitelisted and can make use of iframe freely.

Security?

Simply bypassing the header by removing X-Frame-Options header can be enough for you. But if its bypassed, remember that the browser is vulnerable to attacks which make use of iframes like the famous click-jacking technique. There are many possibilities.

However, you can do this securely by making use of Content-Security-Policy (CSP) header. For instance, if you remove X-Frame-Options, make sure you add / modify CSP header with frame-ancestors directive to whitelist only your origin URL. So, it will not be open for everyone.

Written on March 5, 2018