こんにちは!むちょこです。
PHPが大好きでwebエンジニアやってます。
みなさんはXSS(クロスサイトスクリプティング)ってご存知ですか?
「初めて聞いた!」「名前は知ってるけど、どう対策したら良いかわからない」という方は、ぜひこの機会に覚えていただけたら嬉しいです☆
XSS(クロスサイトスクリプティング)とは
クロスサイトスクリプティングとは、webページを動的に生成するときの脆弱性や、それを利用した攻撃のことです。
英語ではcross site scriptingと書きます。
「それなら略称はCSSじゃないの?」と思いました?私も思いました。
wikipediaによると、CSSだと紛らわしいから使われなくなったそうです。たしかにスタイルシートの方を思い浮かべちゃいそうですね。
かつてはCSSという略称も使われていたが、Cascading Style Sheetsと紛らわしいのでこの略称はあまり使われなくなった。
クロスサイトスクリプティング – Wikipedia
XSSの攻撃例
早速ですが、百聞は一見に如かずということでXSSの攻撃例を見てみましょう。
例えばユーザが入力した任意のニックネームをプロフィールページに表示するアプリケーションがあったとします。
<form action="/setting" method="post">
<input type="text" name="nickname">
<input type="submit" value="保存">
</form>
<h2><?php echo $nickname; ?>さんのプロフィールページ</h2>
このアプリケーションでは、ユーザが入力した文字列がそのまま出力されます。
開発者の意図としては、ここに「むちょこ」などの文字列を入れてほしかったものと思います。
しかし、悪意をもったユーザが例えば以下のような文字列を入れたらどうなるでしょうか。
<script>location.reload();</script>
答えは、該当のプロフィールページにアクセスすると上記のjavascriptが実行され、延々と再読み込みが続きます……(試すときはご自分の管理下のサイトでやってくださいね)。
今回はリロードでしたが、例えば同様の方法で訪問者のcookieを取得することもできますし、たくさんの広告を表示するなんてこともできてしまいます。怖いですね。。
PHPでできるXSS対策
訪問者や管理者に不利益な攻撃から守るために、 PHPでできるXSS対策方法についてご紹介します。
やることは簡単で、影響を及ぼす可能性のある文字列をエスケープしてしまうだけです。さらに、PHPにはそのための関数がすでに用意されているため、たった1行でできてしまいます。
その関数というのが、 htmlentities()です。
htmlentities()は、HTMLとして意味を持つ記号などを無害な代替コードに変換してくれる関数です。
例えば先ほどのスクリプトを$nicknameに代入してhtmentities()の引数として渡すと、このようになります。
<?php echo htmlentities($nickname); ?>
htmlのタグとして認識されるために必要な<と>が、&lt;と&gt;に変換されたためにjavascriptは動作せず、ただの文字列としてブラウザに出力されました。※表示の都合上、&を全角にしてありますが、本来は半角です。
このように、中身が保障されていない値を出力する際にはhtmlentities()を利用することでXSS対策を行うことができます。
htmlentities()では、オプションの第二引数でクォートや無効な符号単位シーケンス、文書型の扱いを指定したり、第三引数で文字コードを指定することができます。
実際のプログラムでは、例えば以下のようなオプションを指定して利用されます( 指定する値は環境によりますが、恐らくこれが一番多いです)。
htmlentities($str, ENT_QUOTES, "UTF-8");
これは、文字コードはUTF-8で、シングルクオートとダブルクオートを共に変換する設定です。
htmlentities()の公式マニュアルはこちら。テンプレートエンジンにおけるXSS対策
htmlentities()でXSS対策を行えば良いことはわかったものの、実際の開発ではテンプレートエンジンを利用することが多いと思います。
主なテンプレートエンジンでは、変数の出力時に自動でhtmlentites()にかけてくれるものもあります。
ここでは3つのテンプレートエンジンについてご紹介します。なお、これからはechoの機能も含まれているので別途echoを書く必要はありません。
Blade(Laravel)
昨今大人気のLaravel用のテンプレートエンジンであるBladeでは、出力したい値を{{ }}で囲うことでエスケープ済みの出力ができます。
{{ $nickname }}
Twig(symfony)
symfonyに搭載されているTwigの場合も、Bladeと同様に{{ }}で囲います。ただし、変数名の前に$を書く必要がないのでご注意ください。
{{ nickname }}
Smarty
新規でSmartyを使うことはもうあまりないと思いますが、レガシーな案件をやるとお目にかかることがあるかもしれません。昔はテンプレートエンジンといえばこれでした。
標準出力の{ }だけではエスケープされないので、きちんと明示する必要があります。
{$nickname|escape:'htmlall'}
その他
他のテンプレートエンジンにもそれぞれ独自のルールがあります。
きちんと公式ドキュメントを確認して、XSS対策が必要な値には必ずエスケープ処理を行ってから出力しましょう☆
お知らせ
テレラボ様に当記事を取り上げていただきました!