こんにちは。かじです。
今回はクロスサイトスクリプティングってなんだっけというテーマで書いていきたいと思います。
徳丸さんの記事を読んでいたら
ある時、フレームワークによる脆弱性対策について下記の記事を読んでいました。
そしたらこんな箇所がありました。
コードは画像だったので引用させていただきます。
SQLインジェクションがあるのは重大な点ですが、ここではひとまず置いておきます。何が問題かというと、インサートする前にHTMLエスケープしてるんですね。これはおかしいです。表示の際にやるべきです。しかし類似例はPHP入門書には極めて多いです。
https://pr.forkwell.com/tech_event_reports/security-study-01/
これを読んで、初学者の時に参考にしていた教材のコードふと思い出しました。その教材では、クロスサイトスクリプティング対策として、POSTメソッドで受け取った値に対して、htmlエスケープ処理を施していました。
はじめに断っておきますが、恥ずかしながら、僕が偉そうに公開しているコードにも、上記のようなエスケープ処理をしている箇所はあるかもしれません。
そのくらい僕はクロスサイトスクリプティングやHTMLエスケープ処理に対してフワフワとした理解しかありませんでした。なのでこの記事を読んだ時に半分理解したような半分理解していないような感覚でした。
クロスサイトスクリプティングってなんだっけ
ここで一度クロスサイトスクリプティングって何かをおさらいしてみましょう。
IPAのサイトではこう書いてあります。画像も引用させていただきます。
ウェブアプリケーションの中には、検索のキーワードの表示画面や個人情報登録時の確認画面、掲示板、ウェブのログ統計画面等、利用者からの入力内容やHTTPヘッダの情報を処理し、ウェブページとして出力するものがあります。
https://www.ipa.go.jp/security/vuln/websecurity/cross-site-scripting.html
Xやfacebook、掲示板サイトのようなユーザーが投稿したコンテンツを別のユーザーに向けて表示するというアプリケーションを想像してください。ユーザーが投稿したコンテンツにJavaScriptのプログラムが含まれていた場合、そのコンテンツを別のユーザーが表示すると、コンテンツと共にJavaScriptのプログラムが実行されます。
これが、無害なconsole.logとかalertみたいなプログラムだったらいいですが、変なサイトに誘導されたり、ajaxでクライアント側のデータを盗まれたりといったことができてしまいます。
これがクロスサイトスクリプティングになります。
htmlspecialchars関数の機能を見直す
それではhtmlspecialchars関数についておさらいしてみましょう。
僕は、PHPerなので、PHPで説明しますが、他の言語の場合は、同様の関数に置き換えてみてください。
PHPの公式ドキュメントには下記のように記載されています。
文字の中には HTML において特殊な意味を持つものがあり、 それらの本来の値を表示したければ HTML の表現形式に変換してやらなければなりません。 この関数は、これらの変換を行った結果の文字列を返します。
https://www.php.net/manual/ja/function.htmlspecialchars.php
『’』『”』『&』『<』『>』といった文字はHTML上では、特殊な意味を持ちます。
わかりやすいところで言うと、『<』『>』は、HTMLタグで使用されます。つまり、『<』『>』が現れるとブラウザは、「あ、HTMLタグだ」と認識してHTMLタグに対する挙動をしてしまいます。
そんな時に、htmlspecialcharsを使用するのです。HTMLエスケープをすることで、HTMLで特殊な意味を持つ文字をHTMLタグとしてではなく、表示する文字としてブラウザで表示できるように別の文字にすることができます。
あまり、実用的な例が思いつかなかったので、適当な例を出しておきます。下記の例だと、<test>がタグとして認識されてしまい、表示されません。
<?php
echo "<test>test";
?>
それを下記のようなエスケープ処理を施すと<test>を画面上に表示することができます。
<?php
echo htmlspecialchars("<test>test");
?>
htmlspecialchar関数自体は、元々クロスサイトスクリプティング用の関数というよりは、HTMLの構文上意味を持つ文字をブラウザで表示できるようにする関数なんだろうなと予想することができます(※根拠はありません)。
クロスサイトスクリプティング対策ではなぜ、htmlspecialcharsをしようするのか
クロスサイトスクリプティングでは、ユーザーが投稿したにJavaScriptが投稿に含まれていたら、そのJavaScriptによって攻撃すると説明しました。
例えば下記のようなスクリプトが投稿に混ざっていたらどうなるでしょうか。$postは、投稿されたコンテンツをデータベースから持ってきた値という体で、見てください。
<?php
$post = "<script>window.alert('あなたのパソコンは乗っ取られました。至急1000億円を振り込んでください。')</script>";
echo $post;
?>
投稿を表示した人はみんな、下記のような脅迫文アラートが表示されてしまいます。
このスクリプトを外部のスクリプトを読み込むような形にしたら、外部からサイトを操作したり、データを盗むことだってできてしまいます。
それを防ぐために使用するのが、HTMLエスケープ処理です。
上記のスクリプトが埋め込まれていた投稿のスクリプトタグをHTMLタグとしてではなく、ただの画面に表示される文字としてブラウザが認識してしまえばいいという訳で、HTMLエスケープ処理を施します。
<?php
$post = "<script>window.alert('あなたのパソコンは乗っ取られました。至急1000億円を振り込んでください。')</script>"
echo htmlspecialchars($post);
?>
こうするとJavaScriptは実行されずに、変数内に格納されていた文字はそのまま画面に表示されます。
これでJavaScriptの実行はされません。
徳丸氏は何を指摘したいのか
冒頭で引用した徳丸氏の記事で、エスケープ処理については、引用部分しか述べられておらず、なぜいけないのかがピンと来ない人もおられるかもしれません。
なので、ここまでのクロスサイトスクリプティングとHTMLエスケープ処理の見直した内容を踏まえて、考えてみたいと思います。
冒頭の引用部分を整理すると徳丸氏の主張は、
・インサートする前にHTMLエスケープ処理をすべきではない
・画面に表示する前にHTMLエスケープ処理をすべき
という二点です。この二点の理由を考えて、徳丸氏の指摘を考えたいと思います。
まず、順番として後者から考えていきたいと思います。HTMLエスケープは前述の通り、ブラウザにHTMLを表示する際に特殊文字をHTML構文要素としてではなく、画面に表示する文字として認識するための処理です。ですので、画面に表示する前にエスケープ処理をすべきであるということは明白になるかと思います。
そして後者です。インサートする前にHTMLエスケープ処理をすべきではない理由を考えると、理解しているつもりではあるのですが、なかなか納得する説明に行きつきません。ひとまず、思いついた理由を書き出していきたいと思います。
一つは、htmlspecialcharsはあくまで、HTMLで特殊文字を表示するための処理なのだから、データベース挿入前に不要な処理をすべきでないということ。本来の意図と異なる形で(何となく)実装をすると、で保守性の低下やバグの原因になります。
例えば、htmlエスケープ処理が至るところにあると、いちいちそれを考慮しなければいけません。
<?php
$post = "XよりTwitterの方がいいんですけど。<br>#Twitter復活を望みます";
$escapedPost = htmlspecialchars($post);
// データベースにインサートし、取り出された過程をショートカット
// 表示時に再度エスケープ処理
echo htmlspecialchars($escapedPost);
?>
当たり前の話ですが、エスケープ処理をしたものをさらにエスケープ処理するとバグります。
それを考慮したプログラムを書かなければいけませんし、テスト工数も膨らみ、バグの温床にもなります。「とりあえず何かよく分からないけど、必要そうだからやっておこう。」という実装は非常に危険なのです。
それではどう対策するのか
サーバーサイドで持っていた値を画面表示する際にクロスサイトスクリプティングを防ぐためにHTMLエスケープを使用すると何度も書いています。そもそも、そういう投稿データを作成したくない場合はどうするか。
そのような場合は、適切な機能やプログラムを使用することが大切です。
例えば、そもそもそういうリクエストはブロックしたいというのであれば、WAFでリクエストボディにクロスサイトスクリプティングを疑われるような内容があればブロックするという方法が挙げられるでしょう。投稿自体は、ブロックはしたくない、内部処理で対処したいのであれば、「投稿コンテンツに<>が含まれた場合は、違う文字列に置き換える」みたいなif文を仕込むという方法になるかもしれません。
どういう意図で何を防ぎたいということを明確にした上で実装することが大切になると思います。
今回はここまでになります。最後まで閲覧くださりありがとうございました。