同じ/異なるオリジンのiframeの中からできること

web

同じ/異なるオリジンのiframeの中からできることを確認する。同じオリジンというのは ホストだけではなくプロトコルやポート番号も同じということ。

検証用ページ

3つのiframeがあるページを作った。 それぞれabout:blankで動的に書き込むのと、同じオリジンのhtmlを参照しているものと、異なるオリジンのhtmlを参照しているもの。

$ cat index.html
<html>
<head>
<style type="text/css">
p{
  width:100px;
  height:100px;
  background:#999;
}
</style>
</head>
<body>
  <p>parent</p>
  <div><iframe src="about:blank" id="if1"></iframe></div>
  <script type="text/javascript">
    var parentValue = "PARENT";
    window.addEventListener("message", (event) => {
      console.log(`message from ${event.origin}: ${event.data}`);
    }, false);
  </script>

  <div><iframe src="./iframe.html"></iframe></div>
  <div><iframe src="https://*****.ngrok.io/iframe.html"></iframe></div>
  <script type="text/javascript" src="./index.js"></script>
</body>
</html>

1つ目のiframeの中にscriptタグなどを書き込むJS。

$ cat index.js
const el = document.getElementById("if1");
const doc = el.contentDocument;

const p = doc.createElement('p');
p.textContent = "child";
doc.body.appendChild(p);

var scriptTag = doc.createElement('script');
const script = `
    var childValue = "CHILD";
    console.log(location.href + " parentValue: " + window.parent.parentValue);
    window.frameElement.style.width = '400px';
`;
scriptTag.innerHTML = script;
doc.body.appendChild(scriptTag);
console.log(`${location.href} childValue: ${el.contentWindow.childValue}`);

iframeのsrcで参照するhtml。

$ cat iframe.html
<html>
<head>
</head>
<body>
  <p>child</p> 
  <script type="text/javascript">
    try{
      console.log(`${location.href} parentValue: ${window.parent.parentValue}`);
    } catch (e) {
      console.log(`${location.href} ${e}`);
    }
    window.parent.postMessage("aaaa", "*");
    if (window.frameElement) {
      window.frameElement.style.width = '400px';
    } else {
      console.log(`${location.href} window.frameElement is ${window.frameElement}`);
    }
  </script>
</body>
</html>

実行

異なるホストにするためngrokを立ち上げる。

$ npm install http-server -g
$ http-server -p 3000
$ ngrok http 3000
$ open http://localhost:3000

検証用ページ

about:blank parentValue: PARENT
http://localhost:3000/ childValue: CHILD
http://localhost:3000/iframe.html parentValue: PARENT
message from http://localhost:3000: aaaa
https://*****.ngrok.io/iframe.html SecurityError: Blocked a frame with origin "https://*****.ngrok.io" from accessing a cross-origin frame.
https://*****.ngrok.io/iframe.html window.frameElement is null
message from http://localhost:3000: 
message from https://*****.ngrok.io: aaaa

about:blankは表示ページと同じオリジンになるので、srcが同じオリジンのURL(./iframe.html)の場合と同じ挙動でparentの値やiframeを参照したり書き換えることができる。 Web広告の標準規格を定めるIABBest Practices for Rich Media Adsで定められているFriendly IFrame(FIF)ではabout:brankのiframeの中に、変数inDapIF = trueを定義してFIF内で実行されていることを分かるようにして広告のスクリプトをsrcとしたscriptタグを書くことで、 スクリプトにdocument.write()のようなページの描画に問題を起こし得るコードや、グローバル変数・CSSが含まれていたとしても、iframeで影響を分離しながら計測や表示領域の拡大縮小などはできるようにしている。 裏を返せばやろうと思えば何でもできるので悪意のあるスクリプトに対する防衛策にはならない。

オリジンが異なる場合は値を参照しようとするとブロックされる。 ただしparentのwindow自体は参照できるのでpostMessage()で外側とやりとりすることはできる。 これを利用したのが同じくIABが定めたSafeFrameで、 iframeを異なるオリジンにして直接外側に干渉させず、SafeFrame APIを介して拡大などさせることでセキュアにする。

ブラウザのwindow間の値渡し - sambaiz-net

参考

SafeFrame ver1.1 仕様読解、媒体側の実装例