用户脚本绕过访问嵌套 iframe 的同源策略

Posted

技术标签:

【中文标题】用户脚本绕过访问嵌套 iframe 的同源策略【英文标题】:Userscript to bypass same-origin policy for accessing nested iframes 【发布时间】:2018-01-23 22:33:06 【问题描述】:

在下面的 html 模型中,嵌套的 iframe 来自不同的子域。这会导致诸如 error: Permission denied to access property "document"

之类的消息
<html>
<head>
    <title></title>
</head>
<body>
    <div>
        <iframe id="outer_iframe_1" src="https://subdomain1.example.com"></iframe>
    </div>
    <div>
        <iframe id="outer_iframe_2" src="https://subdomain2.example.com">
        <div>
            <iframe id="inner_iframe_2" src="https://subdomain4.example.com"></iframe>
        </div>
        </iframe>
    </div>
    <div>
        <iframe id="outer_iframe_3" src="https://subdomain3.example.com"></iframe>
    </div>
</body>
</html>

我打算使用用户脚本在嵌套 iframe(例如inner_frame_2)中获取和修改值,因此应该可以绕过同源策略。但是GM_xmlhttpRequest 的示例似乎依赖于 GET/POST 请求,而我只想处理这些 iframe 中已经加载的页面数据。

我误解了GM_xmlhttpRequest,还是我应该在这里采取其他方法?

【问题讨论】:

您可以修改 iframe 的内容(脚本)吗?或者你想远程控制外星站点? @NilsRückmann 不确定我是否理解这个问题,但我无法控制实际站点的代码。希望有帮助! 【参考方案1】:

我认为您可以做到这一点的唯一方法是使用window.postMessage() 方法将带有数据的消息从 iframe 发送到顶部窗口。要捕获 Greasemonkey 脚本中的每个 iframe,请参阅 Brock Adams answer on Apply a Greasemonkey userscript to an iframe?;你必须像这样使用 GM @match 指令:

// @match        http://subdomain1.example.com/*

// @match        *.example.com/*

然后你可以检查当前窗口是否是顶部窗口,和/或检查document.domain来识别iframe:

// ==UserScript==
// @name         New Userscript
// @match        http://main-domain.something
// @match        *.example.com/*
// ==/UserScript==

(function() 
    'use strict';

    if (window.top === window.self) 
        // Here we are at the top window and we setup our message event listener
    
    else 
        // Here we get inside the iframes.
        // We can address and check each iframe url with document.domain
    
)();

我们需要将"message" 的事件挂钩到顶部窗口,该窗口将处理它从 iframe 接收到的每条消息以及数据:

window.addEventListener("message", function(event) 
    // do something with the event.data
, false);

我们可以使用document.domain 来识别 iframe;对 iframe 元素进行我们需要的任何操作;检索我们想要的所有数据并将消息发送到顶部窗口:

window.top.postMessage(
    // data object we send to the top window
, "*");

我创建了一个演示来尝试这个,它工作得很好。我的顶部窗口 URL 是 http://zikro.gr/dbg/gm/iframes/main.php,子域就像 http://subdomain1.zikro.gr/。顶部窗口 HTML 与我的 iframe url 和 GM 脚本相同:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://zikro.gr/dbg/gm/iframes/main.php
// @match        *.zikro.gr/*
// @grant        none
// ==/UserScript==

(function() 
    'use strict';

    if (window.top === window.self) 
        // Here we are at the top window and we setup our message event listener
        document.body.style.backgroundColor = "#f00"; // Just a UI change to identify the top window
        window.addEventListener("message", function(event) 
            window.console.log("This is data from '" + event.data.title +
                               "'; with message '" + event.data.message +
                               "'; with data '" + event.data.data +"'" +
                               "'; from domain '" + event.data.domain + "'");
        , false);
    
    else 
        // Here we get inside the iframes.
        // We can address and check each iframe url with document.domain

        document.body.style.backgroundColor = "#0f0"; // Just a UI change to identify the iframe window

        // We change something inside the iframe
        var dataDiv = document.getElementsByTagName('div')[0];
        dataDiv.innerHTML += " with a change!";

        // And we post a message to the top window with all the data we want inside an object
        window.top.postMessage(
            title: document.title,
            domain: document.domain,
            message: "Hello from, iframe - " + document.title,
            data: dataDiv.innerText
        , "*");
    

)();

还有一个屏幕截图供那些没有安装 Greasemonkey/Tampermoney 的人测试:

PS: 像这样直接在 iframe 标签内添加元素是无效的:

<iframe id="outer_iframe_2" src="https://subdomain2.example.com">
<div>
    <iframe id="inner_iframe_2" src="https://subdomain4.example.com"></iframe>
</div>
</iframe>

【讨论】:

您的示例页面不再工作,可能是因为首页被强制设置为 https 并且 iframe 有一个 http 地址。顺便说一句,在找到您的原始回复之前,我在互联网上至少找到了 3 份副本;-) @close 没有子域,因为我已经移动了服务器并且没有备份这些子域。我会尝试修复它或删除整个演示部分。 我仍然很难理解为什么你不得不使用如此极端的膨胀来做如此简单而普通的事情。 @Akito 欢迎您写下您自己的答案,并启发我们所有人让事情变得简单。请继续。【参考方案2】:

这不是对您问题的直接回答,但适用于那些想要使用 javascript 来操纵网页以进行数据处理的人。

像 PhantomJS 这样的软件专为“浏览器自动化”而设计,允许完全删除跨源策略。

phantomjs.exe --web-security=no script.js

在你的脚本中,你可以使用

page.open("http://fiddle.jshell.net/9aQv5/show/", function(status)  // Load a webpage
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", function()  // Add support jor jQuery
        page.evaluate(function()  // Run custom script
            console.log($("body").find("iframe").attr("src"));

            console.log($("body").find("iframe").contents().find("iframe").attr("src"));

            console.log($("body").find("iframe").contents().find("iframe").contents().find("#about-puppy-linux").html());
        );
        phantom.exit(0);
    );
);

你得到以下输出:

//fiddle.jshell.net/9aQv5/show/light/
http://www.puppylinux.com/
About Puppy Linux

【讨论】:

我认为这只会消除您特定用户脚本的安全性(无论如何您都必须信任)?尽管如此,window.postMessage() 方法似乎更便携,因为它不需要用户脚本主机来规避策略。

以上是关于用户脚本绕过访问嵌套 iframe 的同源策略的主要内容,如果未能解决你的问题,请参考以下文章

禁用 Firefox 同源策略

有没有办法绕过 Javascript / jQuery 的本地访问同源策略?

无法访问 iframe 内容(同源策略)

将vue使用iframe嵌套至html中使用(页面交互传值)

同源和跨域

开发机上绕过Chrome同源策略的办法