SecurityError: a empêché une trame d'origine d'accéder à une trame d'origine croisée

556

Je charge un <iframe>dans ma page HTML et j'essaie d'accéder aux éléments qu'il contient en utilisant Javascript, mais lorsque j'essaie d'exécuter mon code, j'obtiens l'erreur suivante:

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

Pouvez-vous m'aider à trouver une solution pour accéder aux éléments du cadre?

J'utilise ce code pour les tests, mais en vain:

$(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!";
    });
});
mubashermubi
la source

Réponses:

821

Politique de même origine

Vous ne pouvez pas accéder à une <iframe>origine différente en utilisant JavaScript, ce serait une énorme faille de sécurité si vous pouviez le faire. Pour la politique de même origine, les navigateurs bloquent les scripts essayant d'accéder à un cadre avec une origine différente .

L'origine est considérée comme différente si au moins l'une des parties suivantes de l'adresse n'est pas conservée:

<protocol>://<hostname>:<port>/...

Le protocole , le nom d'hôte et le port doivent être identiques à ceux de votre domaine, si vous souhaitez accéder à une trame.

REMARQUE: Internet Explorer est connu pour ne pas suivre strictement cette règle, voir ici pour plus de détails.

Exemples

Voici ce qui se passerait en essayant d'accéder aux URL suivantes à partir de http://www.example.com/home/index.html

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 

solution de contournement

Même si la stratégie de même origine empêche les scripts d'accéder au contenu des sites d'origine différente, si vous possédez les deux pages, vous pouvez contourner ce problème en utilisant window.postMessageet son messageévénement relatif pour envoyer des messages entre les deux pages, comme ceci:

  • Dans votre page principale:

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

    Le deuxième argument de postMessage()peut être '*'d'indiquer aucune préférence quant à l'origine de la destination. Une origine cible doit toujours être fournie lorsque cela est possible, pour éviter de divulguer les données que vous envoyez à tout autre site.

  • Dans votre <iframe>(contenu dans la page principale):

    window.addEventListener('message', event => {
        // IMPORTANT: check the origin of the data! 
        if (event.origin.startsWith('http://your-first-site.com')) { 
            // 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; 
        } 
    }); 
    

Cette méthode peut être appliquée dans les deux sens , en créant également un écouteur dans la page principale et en recevant les réponses du cadre. La même logique peut également être implémentée dans les fenêtres contextuelles et, fondamentalement, dans toute nouvelle fenêtre générée par la page principale (par exemple en utilisant window.open()), sans aucune différence.

Désactiver la politique de même origine dans votre navigateur

Il y a déjà de bonnes réponses à ce sujet (je viens de les trouver sur Google), donc, pour les navigateurs où cela est possible, je lierai la réponse relative. Cependant, n'oubliez pas que la désactivation de la politique de même origine n'affectera que votre navigateur . En outre, l'exécution d'un navigateur avec des paramètres de sécurité de même origine désactivés permet à tout site Web d'accéder à des ressources d'origine croisée, donc c'est très dangereux et ne devrait JAMAIS être fait si vous ne savez pas exactement ce que vous faites (par exemple à des fins de développement) .

Marco Bonelli
la source
27
Toute autre réponse que j'ai trouvée 1 , 2 , suggère que CORS / Access-Control-Allow-Originne s'applique pas aux iFrames, seulement aux XHR, aux polices, à WebGL etcanvas.drawImage . Je crois que postMessagec'est la seule option.
snappieT
370
La première fois que j'ai vu l'opérateur tilde "~" en javascript. Pour toute autre personne qui ne savait pas non plus ce qu'elle fait: elle convertit -1 en 0, ce qui vous évite d'avoir à faire "! = -1" sur le résultat de l'indexOf. Personnellement, je pense que je continuerai à utiliser "! = -1" car il est plus facile pour les autres programmeurs de comprendre et évite les bugs qui viendraient d'oublier de mettre le tilde. (Mais c'est toujours agréable d'apprendre quelque chose de nouveau.)
Redzarf
4
@SabaAhang suffit de vérifier le iframe.src, et si le site est différent du nom d'hôte de votre domaine, vous ne pouvez pas accéder à ce cadre.
Marco Bonelli
18
@Snuggs totalement faux, ~renvoie le complément à 2 du nombre, ndevient ainsi -n-1, ce qui signifie que seul -1deviendra 0(ce qui est interprété comme false), et toute autre valeur passera le test. IE 0 = -(-1)-1, non -(-1+1).
Marco Bonelli
2
@ user2568374 location.ancestorOrigins[0]est l'emplacement du cadre parent. Si votre cadre s'exécute dans un autre site et que vous vérifiez en utilisant event.origin.indexOf(location.ancestorOrigins[0])vous vérifiez si l'origine de l'événement contient l'adresse du cadre du parent, qui sera toujourstrue , par conséquent, vous autorisez tout parent avec n'importe quelle origine à accéder à votre cadre, et cela n'est évidemment pas quelque chose que vous voulez faire. De plus, document.referrerc'est aussi une mauvaise pratique, comme je l'ai déjà expliqué dans les commentaires ci-dessus.
Marco Bonelli
55

Compléter la réponse de Marco Bonelli: la meilleure façon actuelle d'interagir entre les cadres / iframes est utilisée window.postMessage, prise en charge par tous les navigateurs

Geert
la source
21
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien de référence. Les réponses de lien uniquement peuvent devenir invalides si la page liée change. - De l'avis
Alessandro Cuttin
9
Je ne suis pas d'accord, @AlessandroCuttin. Expliquer comment cela window.postMessagefonctionne ne ferait que reproduire la réponse acceptée à laquelle je fais déjà référence. De plus, la valeur essentielle que ma réponse ajoute est exactement celle de référencer la documentation externe.
Geert
5
Je pense que c'est mieux si vous pouvez modifier la réponse acceptée et l'ajouter ici
Martin Massera
12
window.postMessage nous ne pouvons utiliser que si nous pouvons accéder à la fois à l'élément parent (notre page HTML) et à l'élément enfant (autre domaine iframe). Sinon, "IL N'Y A PAS DE POSSIBILITÉ", il générera toujours une erreur "Uncaught DOMException: Blocked a frame" avec l'origine "< votredomaine.com >" d'accéder à une trame d'origine croisée. "
VIJAY P
19

Vérifiez la http://www.<domain>.comconfiguration du serveur Web du domaine car X-Frame-Options il s'agit d'une fonction de sécurité conçue pour empêcher les attaques clickJacking,

Comment fonctionne clickJacking?

  1. La page du mal ressemble exactement à la page de la victime.
  2. Ensuite, il a incité les utilisateurs à saisir leur nom d'utilisateur et leur mot de passe.

Techniquement, le mal a une iframesource sur la page de la victime.

<html>
    <iframe src='victim_domain.com'/>
    <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>

Fonctionnement de la fonction de sécurité

Si vous souhaitez empêcher le rendu de la demande du serveur Web dans un iframeadd the x-frame-options

Options X-Frame DENY

Les options sont les suivantes:

  1. SAMEORIGIN // n'autorise que mon propre domaine à rendre mon HTML à l'intérieur d'un iframe.
  2. DENY // n'autorise pas le rendu de mon HTML dans un iframe
  3. "ALLOW-FROM https://example.com/ " // autoriser un domaine spécifique à afficher mon code HTML dans un iframe

Voici un exemple de configuration IIS:

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

La solution à la question

Si le serveur Web a activé la fonction de sécurité, cela peut provoquer une erreur de sécurité côté client comme il se doit.

Shahar Shokrani
la source
1
Je ne pense pas que les options X-Frame s'appliquent ici - les options X-Frame définies par la page invité (intégrée) peuvent entraîner le refus du parent de charger la page, mais pour autant que je sache, cela n'affecte pas le javascript accès - même avec X-Frame-Options: *, je ne pense pas que vous serez en mesure d'accéder au DOM d'une page d'invité d'origine différente avec javascript
Noah Gilmore
13

Pour moi, je voulais implémenter une poignée de main à 2 voies, ce qui signifie:
- la fenêtre parent se chargera plus rapidement que l'iframe
- l'iframe devrait parler à la fenêtre parent dès qu'elle est prête
- le parent est prêt à recevoir le message iframe et à rejouer

ce code est utilisé pour définir une étiquette blanche dans l'iframe à l'aide du code [propriété personnalisée CSS]
:
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","*");
    }
});

parent

$(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);
});

naturellement, vous pouvez limiter les origines et le texte, c'est du code facile à travailler,
j'ai trouvé cet examen utile:
[Messagerie interdomaines avec postMessage]

Yakir Manor
la source
je fais face à un problème avec safari où le document dans iframe exécute son JS plus tard que la page parent, ce qui provoque l'envoi du message plus tôt que le document dans iframe n'écoute les messages; ce qui est exactement à l'opposé de ce que font Chrome et Firefox - avez-vous testé votre code en safari sur iOS? btw postMessage avec le deuxième paramètre de valeur "*" n'est pas tout à fait sûr, vous devez toujours spécifier le domaine
sKopheK
Votre premier bloc de code est-il sur l'iframe dans le parent ou est-ce sur la page qui est chargée dans l'iframe?
Demonic218
0

Je voudrais ajouter une configuration spécifique à Java Spring qui peut affecter cela.

Dans le site Web ou l'application Gateway, il existe un paramètre contentSecurityPolicy

au printemps, vous pouvez trouver l'implémentation de la sous-classe 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)

Le navigateur sera bloqué si vous n'avez pas défini de contenu externe sécurisé ici.

ssp
la source
0

Si vous contrôlez le contenu de l'iframe - c'est-à-dire s'il est simplement chargé dans une configuration d'origine croisée comme sur Amazon Mechanical Turk - vous pouvez contourner ce problème avec l' <body onload='my_func(my_arg)'>attribut pour le html interne.

Par exemple, pour le html interne, utilisez le thisparamètre html (oui - thisest défini et fait référence à la fenêtre parent de l'élément de corps interne):

<body onload='changeForm(this)'>

Dans le html intérieur:

    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>
Zhanwen Chen
la source
-25
  • Ouvrez le menu de démarrage
  • Tapez windows + R ou ouvrez "Exécuter
  • Exécutez la commande suivante.

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

sakthi sudhan
la source
3
Bon pour un test rapide et sale!
user1068352
6
Terrible pour tout ce qui n'est pas un test rapide et sale ... et déjà l'adresse dans la réponse acceptée.
Quentin
2
Même avec la commande, cela ne fonctionne pas car Chrome évite de désactiver la sécurité Web de cette façon
Metafaniel