必ずやろう!PHPで行うXSS対策

こんにちは!むちょこです。

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()の引数として渡すと、このようになります。

htmlentities使用例
<?php echo htmlentities($nickname); ?>
ブラウザの表示
ソース

htmlのタグとして認識されるために必要な<と>が、&lt;と&gt;に変換されたためにjavascriptは動作せず、ただの文字列としてブラウザに出力されました。※表示の都合上、&を全角にしてありますが、本来は半角です。

このように、中身が保障されていない値を出力する際にはhtmlentities()を利用することでXSS対策を行うことができます。

MEMO

htmlentities()では、オプションの第二引数でクォートや無効な符号単位シーケンス、文書型の扱いを指定したり、第三引数で文字コードを指定することができます。

実際のプログラムでは、例えば以下のようなオプションを指定して利用されます( 指定する値は環境によりますが、恐らくこれが一番多いです)。

htmlentities($str, ENT_QUOTES, "UTF-8");

これは、文字コードはUTF-8で、シングルクオートとダブルクオートを共に変換する設定です。

htmlentities()の公式マニュアルはこちら。

http://php.net/manual/ja/function.htmlentities.php

テンプレートエンジンにおけるXSS対策

htmlentities()でXSS対策を行えば良いことはわかったものの、実際の開発ではテンプレートエンジンを利用することが多いと思います。

主なテンプレートエンジンでは、変数の出力時に自動でhtmlentites()にかけてくれるものもあります。

ここでは3つのテンプレートエンジンについてご紹介します。なお、これからはechoの機能も含まれているので別途echoを書く必要はありません。

Blade(Laravel)

昨今大人気のLaravel用のテンプレートエンジンであるBladeでは、出力したい値を{{ }}で囲うことでエスケープ済みの出力ができます。

bladeでの出力例
{{ $nickname }}

Twig(symfony)

symfonyに搭載されているTwigの場合も、Bladeと同様に{{ }}で囲います。ただし、変数名の前に$を書く必要がないのでご注意ください。

twigでの出力例
{{ nickname }}

Smarty

新規でSmartyを使うことはもうあまりないと思いますが、レガシーな案件をやるとお目にかかることがあるかもしれません。昔はテンプレートエンジンといえばこれでした。

標準出力の{ }だけではエスケープされないので、きちんと明示する必要があります。

smartyでの出力例
{$nickname|escape:'htmlall'}

その他

他のテンプレートエンジンにもそれぞれ独自のルールがあります。

きちんと公式ドキュメントを確認して、XSS対策が必要な値には必ずエスケープ処理を行ってから出力しましょう☆