概要
- Shadow DOMで、一応見えなくなるよ
- 別オリジンのページをiframe内にロードすると、一応見えなくなるよ
- 弱点の対策と、レイアウト調整が面倒だよ
背景
ユーザ(Webブラウズしている人)には見せたいが、他には漏らしたくない情報があるとします。この情報を見せようと、素直にDOMに追加すると、同じページの他のスクリプトに丸見えで、漏れてしまいます。
自作Webサイトの場合
この場合、情報を漏らさないスクリプトのみを選べるため、理論上は機密が保たれます。
フィクションで「理論上」はだいたいフラグですが、現実でも様々な地雷が埋まっており、理論通りにするのは言うほど簡単ではありません。
スクリプトを選べない例外として、クロム拡張などのブラウザ拡張機能があります。変な拡張機能を使って漏れたら自己責任という見方もありますが、そう単純ではありません。
SNSサービスの場合、漏れる情報はユーザ個人だけでなく、共有する他ユーザの情報も混ざっています。あるユーザのミスで、何の落ち度もない知り合いのユーザの情報が漏れる様では、安心してサービスを利用できません。
このため、SNSサービスの場合は機密を守る対策が必要になります。もちろん、SNSサービス以外も機密を守る対策をする方が良いです。
クロム拡張の場合
クロム拡張の場合、事態はより複雑です。ページ本体に寄生する形になるため、他のスクリプトを選べません。
特に、私のクロム拡張の様に任意のWebページで動かす場合、悪意を持ったスクリプトも混ざってくる事を覚悟しなければなりません。
機密を守る道具とその弱点
Shadow DOM
Shadow DOMとは?
一言で言うと、DOMの一部を秘密にする機能です。
2つのモードがあり、それぞれ機密性が異なります。
mode | 概要 | 特徴 |
---|---|---|
open | かくれんぼレベル | getElement~では見つからないが、親要素.shadowRoot で見つかる |
closed | スパイレベル | 親要素.shadowRootでもnullが返る |
Shadow DOMで残るリスク
Shadow DOM(closed)さえ使えば、誰でも簡単にスネークできると思ったら、大間違いです。このままでは段ボールは穴だらけです。
JavaScript言語によるリスク
JavaScriptはプロトタイプの操作により、同種のオブジェクト全てを好きに改造できるので、悪用すると…
const originalFunc = Element.prototype.attachShadow;
Element.prototype.attachShadow = () => {
return originalFunc( { mode: "open" } );
};
前もって、この仕込みを他のスクリプトが行っていると、closedのつもりがopenのままという事になります。
幸いなことに、クロム拡張のJavaScript環境は、同一ページの他のスクリプトから隔離されており、変数やプロトタイプは全て別扱いです。このため、クロム拡張でattachShadowする限り、このリスクは管理可能です。
うっかりや好奇心のリスク
クロム拡張なら安心してShadow DOMで機密を扱えると思ったら、大甘です。
DOM自体から漏れなくても、スクリプト本体が、エラーメッセージなどでうっかり漏らすリスクがあります。
好奇心の強いユーザが開発者ツールで、スクリプトが持っている機密をDOMなどに書き出してしまうかもしれません。
つまり、他にどんなスクリプトが動いているか分からないページ(≒コンテンツスクリプト)で機密を扱うこと自体がリスクです。
iframeに別オリジンのページ
オリジンとは?
Web上での、情報を遮断する壁の様なものです。
- 同一オリジン内だと、自由に情報交換可能
- 別オリジンとは、明示的な許可が無いと情報交換不可
クロム拡張だと、コンテンツスクリプトは閲覧中のページのオリジンで、それ以外のスクリプトはクロム拡張独自のオリジンで動作します。つまり、機密情報を持つクロム拡張独自のページを作ってコンテンツスクリプトでiframe内にロードすれば、コンテンツスクリプトは直接機密情報を持たずに済みます。
iframeに別オリジンのページで残るリスク
自分のスクリプトがiframe内にロードできるという事は、同じページの他のスクリプトからもロード可能という事になります。対策をしないと、クリックジャッキングの餌食です。
対策には色々ありますが、ロード時のパラメタなどで、正規の呼び出しかをチェックするのが、一番シンプルでしょう。
iframeに別オリジンページだとレイアウト調整が面倒
同一DOM内だと、CSSを用いてレイアウトやサイズの調整が容易です。しかし、別オリジンページのiframeだと、情報が遮断されるため、これらの調整を自分でやる必要があります。
ちょっとした調整なら、iframe-resizerを使うのも手です。
私のクロム拡張の場合、状況に応じて双方向にサイズ情報をやり取りする必要があったので、iframe-resizerでは機能不足でした。独自にchrome.runtime.connectでサイズ情報のやり取り用チャンネルを作ることで対応しました。