【入門編】Trusted Types APIによるDOMベースXSSの根本的排除 – アプリケーションセキュリティ & 安全な開発防御ガイド

Trusted Types APIでDOMベースXSSを「もう怖くない!」に変える方法:泥棒も諦める家の鍵の話

やあ、みんな!セキュリティの世界へようこそ!

「インジェクション攻撃」とか「XSS(クロスサイトスクリプティング)」って聞くと、なんだか難しそう…って思っちゃうかもしれませんよね。でも、大丈夫!今日のブログでは、そんなちょっと怖い攻撃から、僕たちのウェブサイトやアプリを守るための、とっても強力で、でも実は身近な「鍵」の話をしていきたいと思います。

特に今回は、ブラウザが「これは危ないよ!」って教えてくれる、新しい仕組み、「Trusted Types API」について、新人のIT担当者さんや、これからセキュリティを学び始める開発者さんにも「なるほど!」って理解してもらえるように、泥棒が諦めるような家の鍵に例えながら、じっくり紐解いていきましょう。

そもそも、インジェクション攻撃って何?泥棒に例えてみよう!

まず、攻撃のメカニズムを理解するために、身近な「泥棒」に例えてみましょう。

SQLインジェクション:勝手に家の設計図を書き換えようとする泥棒

皆さんのウェブサイトやアプリは、お客さんの情報(名前、住所、パスワードなど)をデータベースという「金庫」にしまっています。この金庫を開けたり、中身を操作したりするための「合言葉」がSQLという言語なんです。

SQLインジェクション攻撃は、この「合言葉」の隙間を狙って、泥棒が「勝手に設計図を書き換えて、金庫の鍵をこじ開けたり、中身を全部盗み出したりしよう!」と企むようなものです。

例えば、あなたが「検索窓」に「山田」と入力して、データベースから「山田さん」の情報を探そうとしたとします。本来なら、データベースは「山田」という名前の人を探しに行ってくれるはずですよね。

でも、悪意のある人が検索窓にこんなものを入力したらどうでしょう?

' OR '1'='1

もし、この入力がそのままデータベースに渡されてしまうと、データベースは「あれ?『山田』じゃなくて、『’ OR ‘1’=’1’』って何だろう?もしかして、この条件は常に正しい(『1』は『1』と常に等しいから)ってことかな?だったら、全部の情報を見せてあげよう!」なんて勘違いしてしまうかもしれません。

これは、まるで泥棒が家の設計図に「この壁は壊してもいいよ」とか「このドアは開けっ放しにしておいて」なんて勝手に書き足して、侵入経路を確保しようとするのに似ています。

OSコマンドインジェクション:家のドアを勝手に開けようとする泥棒

ウェブサイトやアプリの中には、サーバーという「家」の中で、色々な「仕事」をさせるために、OS(オペレーティングシステム)という「家のお手伝いさん」に命令を出すことがあります。例えば、「このファイルを持ってきて」とか「このプログラムを動かして」といった命令です。

OSコマンドインジェクション攻撃は、この「お手伝いさん」への命令に、泥棒が「ついでにこれもやっておいて!」と、悪意のある命令を紛れ込ませようとする攻撃です。

例えば、あなたが「ファイル名を入力して、そのファイルの中身を表示する」という機能を作ったとします。

本来なら、あなたが「report.txt」と入力すると、サーバーは「report.txt」というファイルの中身を表示してくれるはずですよね。

でも、悪意のある人がファイル名にこんなものを入力したらどうでしょう?

report.txt; rm -rf /

もし、この入力がそのままOSに渡されてしまうと、OSは「report.txtを表示して、そのあとで…おっと、『rm -rf /』?これは、この家(サーバー)にあるものを全部消しちゃえ!っていう命令だ!」と、大変なことをしてしまうかもしれません。

これは、まるで泥棒が家のドアの鍵穴に、変な道具を差し込んで「開けろ!」と無理やりこじ開けようとしたり、家の住人(あなた)に「この部屋の鍵を渡せ!」と脅迫したりするのに似ています。

DOMベースXSS:窓ガラスを割って、中のものを盗む泥棒

さて、今日の本題である「DOMベースXSS」は、ちょっと違うタイプの泥棒です。

「DOM」というのは、ウェブページがブラウザ上でどのように表示されるか、その「骨組み」や「部品」のことだと考えてください。例えば、「この場所に文字を表示する」「このボタンをクリックしたら、この画像を見せる」といった、ウェブページを構成する要素の集まりです。

DOMベースXSS攻撃は、この「骨組み」や「部品」の隙間を狙って、泥棒が「窓ガラスを割って、中にいる人に『この怪しい薬を飲んで!』とか『この怪しいURLをクリックして!』って、悪意のある指示を送りつける」ような攻撃です。

どういうことかというと、悪意のある人が、あなたのウェブサイトのあるページに、巧妙に細工されたURLを仕込みます。そして、あなたがそのURLをクリックして、そのページにアクセスすると、ブラウザは「あ、このURLには怪しい指示が書いてあるぞ!」と、その指示通りに動いてしまうんです。

例えば、こんなURLを想像してみてください。

https://your-website.com/page?message=

このURLの ?message= の後にある が、まさに泥棒からの「怪しい指示」です。もし、あなたのウェブサイトがこの「message」という部分を、そのままブラウザに表示してしまうような作りになっていると、ブラウザは「あ、ここに『XSS!』って表示しろっていう指示があるな」と、そのまま実行してしまい、画面に「XSS!」というアラートが表示されてしまうんです。

これは、まるで泥棒があなたの家の窓ガラスに「ここを叩け!」と書いた紙を貼っておいて、あなたがうっかりそれを叩いてしまうと、窓ガラスが割れて、泥棒が中に入ってきやすくなる、という状況に似ています。

このDOMベースXSSの厄介なところは、サーバー側でいくら対策をしても、ブラウザ側で「ユーザーが操作した結果、危険なコードが実行されてしまう」という点なんです。

Trusted Types API:泥棒も諦める、魔法の「鍵」と「鍵穴」

そこで登場するのが、今日の主役「Trusted Types API」です!これは、ブラウザが「これは危ないよ!」って、危険なコードが実行されるのを未然に防いでくれる、とっても賢い仕組みなんです。

Trusted Types APIは、大きく分けて二つの考え方で機能します。

1. 「安全なものだけ」を「安全な場所」に流す(型付けの考え方)
2. 「型」が合わないものは「通さない」

これを、家の防犯に例えてみましょう。

泥棒も諦める、厳重な「鍵」と「鍵穴」

Trusted Types APIは、まるで「この鍵(Trusted Type)じゃないと、この鍵穴(安全な入出力先)は開かない」という仕組みを作っているようなものです。

  • Trusted Type(安全な鍵): これは、ブラウザが「これは安全なデータだよ!」と、あらかじめ「お墨付き」を与えたデータのことです。あたかも、あなたの家の「合鍵」が、特別な素材で作られていて、他の鍵では絶対に開かないようになっているようなイメージです。
  • 安全な入出力先(安全な鍵穴): これは、ウェブページ上で「危険なコードが実行される可能性のある場所」のことです。例えば、HTMLの要素に文字列を直接書き込んだり、JavaScriptの eval() 関数を使ったりする場所がこれにあたります。これは、あなたの家の「玄関の鍵穴」や「勝手口の鍵穴」のような、外部から侵入されやすい場所だと考えてください。

Trusted Types APIを導入すると、ブラウザは「この『安全な鍵』だけが、この『安全な鍵穴』を通れるよ」というルールを設けます。

もし、泥棒が「普通の鍵(安全でないデータ)」を使って「安全な鍵穴」を開けようとしても、それは「鍵が違う!」と跳ね返されて、侵入を許さないんです。

具体的な仕組み:trustedTypes.createPolicy と setHTML

Trusted Types APIは、JavaScriptのコードで設定します。

まず、trustedTypes.createPolicy() というもので、「これは安全なデータですよ」という「鍵(ポリシー)」を作成します。このポリシーには、どのようなデータが安全なのかを定義します。

// 「my-policy」という名前で、安全なデータを作成するルール(鍵)を定義します。
// ‘html’ は、HTMLとして安全に扱えるデータを作成する、という指定です。
const myTrustedHTML = trustedTypes.createPolicy(‘my-policy’, {
createHTML: (string) => DOMPurify.sanitize(string) // 外部ライブラリDOMPurifyでさらに安全性を高める例
});

この例では、DOMPurify という、HTMLを無害化してくれる便利なライブラリを使っています。これで、たとえ悪意のあるコードが含まれていても、無害なものに変換してくれるので、より一層安心ですよね。

次に、この作成した「安全な鍵」を使って、ウェブページにデータを挿入します。

// ユーザーからの入力を想定した、かもしれない「危険な文字列」
const userInput = ‘‘;

// 作成した「安全な鍵」(myTrustedHTML)を使って、HTMLとして安全なデータを作成します。
// この時点では、まだブラウザに直接表示されるわけではありません。
const sanitizedHTML = myTrustedHTML.createHTML(userInput);

// そして、この「安全なデータ」を、安全な入出力先(innerHTMLなど)に渡します。
// もし、ここで「安全でないデータ」を直接渡そうとすると、ブラウザはエラーを出します。
const element = document.getElementById(‘my-output-area’);
element.innerHTML = sanitizedHTML; // ここで、安全なデータが使われ、安全な場所(innerHTML)に挿入されます。

もし、あなたが myTrustedHTML.createHTML(userInput) を使わずに、直接 userInput を element.innerHTML に渡そうとすると、ブラウザは「おいおい、そのデータは『安全な鍵』で認証されてないぞ!危険だ!」と、エラーを出して実行を止めてくれます。

まるで、泥棒が「普通の合鍵」で玄関の鍵を開けようとしたら、「ピピピッ!不正解!」とアラームが鳴り響くようなものです。

モダンブラウザでの実装戦略:どこから始める?

「Trusted Types API、すごいのはわかったけど、どうやって導入すればいいの?」って思いますよね。

1. CSP(Content Security Policy)の設定:まずは「許可リスト」を作る

Trusted Types APIを有効にするためには、まず CSP(Content Security Policy) という、ブラウザへの「お約束」を設定する必要があります。これは、ウェブサイトがどのようなリソース(画像、スクリプトなど)を読み込んでも良いか、というルールをブラウザに伝えるものです。

CSPは、HTTPヘッダーやHTMLのmetaタグで設定できます。

// HTTPレスポンスヘッダーで設定する場合
Content-Security-Policy: require-trusted-types-for ‘script’; trusted-types default


  • require-trusted-types-for 'script';: これは、「JavaScriptでコードを実行する際には、必ずTrusted Types APIを使って、安全なデータだけを使うように!」という指示です。
  • trusted-types default: これは、「デフォルトで、すべてのTrusted Typeポリシーを有効にするよ」という意味です。

このCSPを設定することで、ブラウザはTrusted Types APIのルールを強制するようになります。

2. 既存コードの「安全な入出力先」を特定する

いきなりすべてのコードをTrusted Types APIに対応させるのは大変!まずは、どこで「危険なコードが実行される可能性のある場所」があるのかを特定することから始めましょう。

  • innerHTML や outerHTML を使っている箇所
  • document.write() を使っている箇所
  • eval() や setTimeout(), setInterval() に文字列を渡している箇所

などが、特に注意が必要な場所です。

3. Trusted Type ポリシーを作成し、コードを修正していく

特定した「安全な入出力先」に対して、Trusted Types APIを使って「安全な鍵」を作成し、コードを修正していきます。

例えば、element.innerHTML = userSuppliedString; というコードがあった場合、以下のように修正します。

// まず、信頼できるHTMLを生成するポリシーを作成します。
// ここでは、入力された文字列をそのまま使うのではなく、
// 悪意のあるコードを無害化する処理(例:DOMPurify)を入れるのが一般的です。
const myHTMLPolicy = trustedTypes.createPolicy(‘my-html-policy’, {
createHTML: (string) => {
// ここで、安全化処理を行います。
// 例: DOMPurify.sanitize(string)
// 今回はシンプルに、そのまま返す例とします。
return string;
}
});

// ユーザーからの入力を取得
const userSuppliedString = getUserInput(); // 実際には、安全な方法で入力を取得してください。

// 作成したポリシーを使って、安全なHTMLオブジェクトを作成
const safeHTML = myHTMLPolicy.createHTML(userSuppliedString);

// そして、安全な入出力先に渡します。
const targetElement = document.getElementById(‘output-container’);
targetElement.innerHTML = safeHTML;

最初はエラーがたくさん出るかもしれませんが、それはブラウザが「ここは危ないよ!」って教えてくれているサインです。一つずつ、安全なコードに修正していくことで、ウェブサイトのセキュリティは格段に向上します。

4. Gradually Rollout(段階的な導入)

いきなり本番環境に導入するのはリスクが高いですよね。まずは開発環境やステージング環境でテストを行い、問題がないことを確認してから、徐々に本番環境に適用していくのがおすすめです。

CSPの設定も、最初は Content-Security-Policy-Report-Only ヘッダーを使って、実際にブロックするのではなく、「もしブロックしたら、こういうレポートが来るよ」という形でテストすることもできます。

まとめ:Trusted Types APIで、未来の攻撃に備えよう!

Trusted Types APIは、DOMベースXSSのような、ブラウザ側で発生する攻撃に対して、非常に強力な防御策となります。まるで、泥棒がどんな手を使っても侵入できないような、最新鋭のセキュリティシステムを導入するようなものです。

最初は少し難しく感じるかもしれませんが、一つずつ「鍵」と「鍵穴」の考え方を理解し、CSPの設定やコードの修正を進めていくことで、あなたのウェブサイトはより安全になります。

「セキュリティって難しい…」と思わずに、「一歩ずつ対策を学んでいきましょう!」という気持ちで、ぜひTrusted Types APIの導入を検討してみてください。あなたのウェブサイトを守るための、強力な味方になってくれるはずですよ!

もし、途中で「あれ?ここどうすればいいんだろう?」って疑問が出てきたら、いつでも聞いてくださいね。一緒に、安全なウェブの世界を作っていきましょう!

コメント

タイトルとURLをコピーしました