安全错误:阻止了来自访问跨源框架的框架的源。

828

我在HTML页面中加载了一个<iframe>,尝试使用JavaScript访问其中的元素,但是当我尝试执行代码时,出现以下错误:

SecurityError: Blocked a frame with origin "http://www.example.com" from accessing a cross-origin frame.

如何才能访问框架内的元素?

我正在使用以下代码进行测试,但无济于事:

$(document).ready(function() {
    var iframeWindow = document.getElementById("my-iframe-id").contentWindow;

    iframeWindow.addEventListener("load", function() {
        var doc = iframe.contentDocument || iframe.contentWindow.document;
        var target = doc.getElementById("my-target-id");

        target.innerHTML = "Found it!";
    });
});

1
window.postMessage() 窗口.postMessage() - sideshowbarker
1
我实际上使用了 window.postMessage(),但在尝试访问目标窗口中的 event.source 时仍然遇到了 OP 提到的错误。 - Leslie Krause
9个回答

1155

同源策略

使用JavaScript无法访问具有不同源的<iframe>,如果可以做到这一点,将会是一个巨大的安全漏洞。对于同源策略浏览器会阻止脚本尝试访问具有不同源的框架。

如果地址的至少一个部分未被维护,则认为来源不同:

协议://主机名:端口号/...

如果您想要访问一个框架,则协议、主机名和端口必须与您的域相同。

注意:众所周知,Internet Explorer 不严格遵循此规则,请参见此处了解详情。

示例

以下是从http://www.example.com/home/index.html尝试访问以下URL时会发生的情况:

URL                                             RESULT
http://www.example.com/home/other.html       -> Success
http://www.example.com/dir/inner/another.php -> Success
http://www.example.com:80                    -> Success (default port for HTTP)
http://www.example.com:2251                  -> Failure: different port
http://data.example.com/dir/other.html       -> Failure: different hostname
https://www.example.com/home/index.html:80   -> Failure: different protocol
ftp://www.example.com:21                     -> Failure: different protocol & port
https://google.com/search?q=james+bond       -> Failure: different protocol, port & hostname

解决方法

虽然同源策略会阻止脚本访问具有不同源的站点的内容,但是如果您拥有这两个页面,您可以使用 window.postMessage 和它的相关message事件来在这两个页面之间发送消息,如下所示:

  • In your main page:

    const frame = document.getElementById('your-frame-id');
    frame.contentWindow.postMessage(/*any variable or object here*/, 'https://your-second-site.example');
    

    The second argument to postMessage() can be '*' to indicate no preference about the origin of the destination. A target origin should always be provided when possible, to avoid disclosing the data you send to any other site.

  • In your <iframe> (contained in the main page):

    window.addEventListener('message', event => {
        // IMPORTANT: check the origin of the data!
        if (event.origin === 'https://your-first-site.example') {
            // The data was sent from your site.
            // Data sent with postMessage is stored in event.data:
            console.log(event.data);
        } else {
            // The data was NOT sent from your site!
            // Be careful! Do not use it. This else branch is
            // here just for clarity, you usually shouldn't need it.
            return;
        }
    });
    

这种方法可以应用于双向,在主页中也创建一个监听器,并从框架中接收响应。同样的逻辑也可以在弹出窗口和基本上由主页生成的任何新窗口(例如使用window.open())中实现,没有任何区别。

禁用同源策略在你的浏览器中

已经有一些关于此主题的好答案(我只是通过谷歌找到的),因此,对于这种可能性的浏览器,我将链接相关答案。但是,请记住,禁用同源策略只会影响您的浏览器。此外,运行具有禁用同源安全设置的浏览器授予任何网站访问跨源资源的权限,因此,如果您不确切知道自己在做什么(例如开发目的),则非常不安全,绝不能这样做


56
我找到的其他答案1, 2都认为CORS/Access-Control-Allow-Origin不适用于iFrames,只适用于XHRs, 字体, WebGL和canvas.drawImage。我认为postMessage是唯一的选择。 - snappieT
5
请检查iframe.src,如果站点与您的域名不同,则无法访问该框架。 - Marco Bonelli
2
对于简单的情况,另一种解决办法是将 GET 参数作为 src 的一部分传递,并通过 location 对象在接收端提取它们,例如 <iframe src='foobar.com/?sCSS=color%3Ared'></iframe> - Daniel Sokolowski
4
location.ancestorOrigins[0] 是父框架的位置。如果您的框架在 另一个站点 中运行,并使用 event.origin.indexOf(location.ancestorOrigins[0]) 进行检查,则正在检查事件的来源是否包含父框架地址,**这将始终为 true**,因此您允许 任何父级任何来源 访问您的框架,这显然不是您想要的。 此外,document.referrer 也是不好的做法,就像我在上面的评论中已经解释过的那样。 - Marco Bonelli
2
@jub0bs 感谢您指出这一点。难以置信这样一段代码居然在那里放了这么长时间。已更新! - Marco Bonelli
显示剩余26条评论

68

19
只有当我们能够访问父级(我们的HTML页面)和子元素(其他域名的iframe)时,才能使用window.postMessage。否则,“没有可能性”,它总是会抛出一个错误:“Uncaught DOMException: Blocked a frame with origin "http://yourdomainname.com" from accessing a cross-origin frame.” - VIJAY P
除了本地文件似乎无法正常工作。如果我在弹出窗口中执行window.opener.postMessage( { }, "*" );,那么父窗口将无法访问event.source。我会收到“阻止了一个带有原点“null”的框架访问跨源框架。” 的消息。这很讽刺,因为文档明确指出postMessage的整个目的是允许不同源之间进行安全通信。 - Leslie Krause
我无法解决“Blocked a frame with origin”浏览器错误。@LeslieKrause,你找到解决办法了吗?我需要显示一个跨域iframe,但是postMessage并没有成功。 - godhar

29

检查域名为http://www.example.com的Web服务器,以查看X-Frame-Options配置。

它是一种安全功能,旨在防止点击劫持攻击。

点击劫持如何工作?

  1. 恶意页面看起来与受害者页面完全相同。
  2. 然后它诱使用户输入他们的用户名和密码。

从技术上讲,该恶意页面有一个带有指向受害者页面源代码的iframe

<html>
    <iframe src='victim-domain.example'/>
    <input id="username" type="text" style="display: none;"/>
    <input id="password" type="text" style="display: none;"/>
    <script>
        //some JS code that click jacking the user username and input from inside the iframe...
    <script/>
<html>

安全功能是如何工作的

如果您想要防止 Web 服务器请求在 iframe 中渲染,可以添加 x-frame-options

X-Frame-Options DENY

可选项包括:

  1. SAMEORIGIN:仅允许我的域名在 iframe 中呈现我的 HTML。
  2. DENY:不允许我的 HTML 在任何 iframe 中呈现。
  3. ALLOW-FROM https://example.com/:允许特定域名在 iframe 中呈现我的 HTML。

这是 IIS 配置示例:

   <httpProtocol>
       <customHeaders>
           <add name="X-Frame-Options" value="SAMEORIGIN" />
       </customHeaders>
   </httpProtocol>

问题的解决方案

如果Web服务器激活了安全功能,这可能会导致客户端出现SecurityError错误,因为这是应该的。


3
我认为X-Frame-Options在这里不适用 - 由嵌入的客户页面定义的X-Frame-Options可能会导致父级拒绝加载页面,但据我所知,它不影响javascript访问 - 即使使用X-Frame-Options:*,我认为您也无法使用javascript访问不同来源的嵌入式页面的DOM。 - Noah Gilmore
这个回答实际上并没有回答问题,问题并没有问这是否安全。 - rafark

19

我希望实现双向握手,即:
- 父窗口将比iframe更快地加载
- iframe应该在准备就绪后立即与父窗口通信
- 父窗口已准备好接收iframe消息并回复

此代码用于使用[CSS自定义属性]设置iframe中的白标签。
代码:
iframe

$(function() {
    window.onload = function() {
        // create listener
        function receiveMessage(e) {
            document.documentElement.style.setProperty('--header_bg', e.data.wl.header_bg);
            document.documentElement.style.setProperty('--header_text', e.data.wl.header_text);
            document.documentElement.style.setProperty('--button_bg', e.data.wl.button_bg);
            //alert(e.data.data.header_bg);
        }
        window.addEventListener('message', receiveMessage);
        // call parent
        parent.postMessage("GetWhiteLabel","*");
    }
});

父级

$(function() {
    // create listener
    var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
    var eventer = window[eventMethod];
    var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
    eventer(messageEvent, function (e) {
        // replay to child (iframe) 
        document.getElementById('wrapper-iframe').contentWindow.postMessage(
            {
                event_id: 'white_label_message',
                wl: {
                    header_bg: $('#Header').css('background-color'),
                    header_text: $('#Header .HoverMenu a').css('color'),
                    button_bg: $('#Header .HoverMenu a').css('background-color')
                }
            },
            '*'
        );
    }, false);
});

自然地,您可以限制源和文本,这是易于使用的代码。
我发现这个例子很有帮助:
[使用postMessage进行跨域消息传递]


我遇到了一个Safari的问题,在其中iframe中的文档比父页面执行其JS稍晚,这会导致消息在发送之前被发送,而iframe中的文档正在监听消息;这与Chrome和Firefox所做的完全相反。你在iOS上的Safari中测试过你的代码吗?顺便说一下,使用值为“*”的second参数的postMessage不是非常安全的,你应该始终指定域。 - sKopheK
你的第一个代码块,是在父级iframe上还是在加载到iframe中的页面上? - Demonic218

3

实际上,对于特定情况,有一种解决方法。

如果您有两个在同一域但不同端口上运行的进程,则两个窗口可以无限制地进行交互。(例如,localhost:3000 & localhost:2000)。要使此工作正常,每个窗口都需要将其域更改为共享的来源:

document.domain = 'localhost'

这也适用于在同一二级域名下使用不同子域名的情况,例如你正在 john.site.example,尝试访问 peter.site.example 或者只是 site.example
document.domain = 'site.example'

通过明确设置document.domain; 浏览器将忽略主机名的差异,Windows可以被视为来自“同一来源”。 现在,在父窗口中,您可以访问 iframe:frame.contentWindow.document.body.classList.add('happyDev')


7
从第106个版本开始,Chrome将禁用修改document.domain。请参见https://developer.chrome.com/blog/immutable-document-domain/。 - user706420

0

我想添加Java Spring特定的配置,以影响此内容。

在网站或网关应用程序中,有一个contentSecurityPolicy设置

在Spring中,您可以找到WebSecurityConfigurerAdapter子类的实现

contentSecurityPolicy("
script-src 'self' [URLDomain]/scripts ; 
style-src 'self' [URLDomain]/styles;
frame-src 'self' [URLDomain]/frameUrl...

...

.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)

如果您没有在此定义安全的外部内容,浏览器将被阻止。


0

当我尝试嵌入一个iframe并在Brave浏览器中打开网站时,我遇到了这个错误。当我将有问题的网站切换到“Shields Down”时,错误消失了。显然,这不是一个完整的解决方案,因为任何使用Brave浏览器访问该网站的人都会遇到相同的问题。要实际解决它,我需要执行此页面上列出的其他操作之一。但至少我现在知道问题出在哪了。


0

如果您可以控制 iframe 的内容 - 也就是说,如果它仅在跨域设置(例如在 Amazon Mechanical Turk 上)中加载 - 您可以使用 <body onload='my_func(my_arg)'> 属性来规避这个问题。

例如,对于内部 html,请使用 this html 参数(是的 - this 已定义,并且它指的是内部 body 元素的父窗口):

<body onload='changeForm(this)'>

在内部 html 中:

    function changeForm(window) {
        console.log('inner window loaded: do whatever you want with the inner html');
        window.document.getElementById('mturk_form').style.display = 'none';
    </script>

-65
  • 打开开始菜单
  • 键入Windows键+R或打开“运行”
  • 执行以下命令。

chrome.exe --user-data-dir="C://Chrome dev session" --disable-web-security


13
如果不是用于快速而粗略的测试,那么对于任何其他用途都很糟糕...这已经在被接受的答案中得到了回应。 - Quentin
5
尽管有这个命令,但它无法生效,因为Chrome会避免以这种方式禁用网络安全性。 - Metafaniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接