目次
- 注意
- はじめに
- クロスサイトスクリプティング(XSS)とは
- 対策
- htmlspecialchars
- テンプレートエンジン
- 誤った対策
- おわりに
どうも、普段はPHPばっかり書いてるumiushiです。
今回から、何回かに分けて「PHPで記述しているときに埋め込みやすい脆弱性」についての解説と、その対策法について語っていきたいと思います。
小むづかしい話もするかもしれませんが、最後までお付き合いいただけたら幸いです。
- SQLインジェクション編
- クロスサイトスクリプティング(XSS)編 ←ココ
- (まだ)クロスサイトリクエストフォージェリ(CSRF)編
- 残りは未定
注意
本記事で紹介する内容は、悪用することで容易にセキュリティインシデントを引き起こす内容が含まれています。
本記事の内容をもとに、自分が管理していないサーバなどへ攻撃をした場合、何かしらの法的措置が行われる可能性がありますので、絶対に行わないようにしてください。
はじめに
近年[いつ?]プログラムの脆弱性をついた攻撃が多くなってきている(ように思う)。
プログラムを書くときには、仕様に沿うように書くのは当然ではあるが、セキュリティにも気をつけて記述する必要がある。
本記事では、発生頻度が高いと思われる脆弱性について、その解説とPHPにおける「脆弱性を発生させないプログラミング」すなわちセキュアプログラミングについて解説する。
PHPerなりたての人や、普段セキュリティをあまり意識せずにプログラミングしている人が対象である。
本記事はPHPでのプログラミングを想定した記事としているが、脆弱性の原理さえわかれば、他の言語でも同様に対応できる(はず)。
クロスサイトスクリプティング(XSS)とは
クロスサイトスクリプティング(XSS) とは、Webサイトの中にHTMLやJavaScriptを埋め込むことでサイト管理者が意図しない動作を引き起こす攻撃手法である。
この攻撃が実行された場合、被害者のCookieを盗み取られたりセッションハイジャックを行われたり、Webサイトを改ざんされたりすることがある。
XSSはユーザから受け取ったデータを画面上に表示する場合に発生する可能性がある。
これはユーザ投稿型のサイトやお問い合わせフォームの確認画面などが挙げられる。
例として、コメントの書き込みおよびコメントを表示するシステムを考える。
<ul> <?php foreach ($comments as $comment): ?> <li><?= $comment ?></li> <?php endforeach; ?> </ul> <form action="comment.php" method="post"> <input type="text" name="comment"> <button>コメントする</button> </form>
このプログラムは$comments
に格納された$comment
を箇条書きで表示するものであるが、XSS脆弱性を含む内容となっている。
ここで、悪意あるユーザが次の内容をコメントとして投稿した場合について考える。
<script>alert(1);</script>
このコメントをPHPで表示した場合、ブラウザ上では次のHTMLとして解釈される。
<ul> <li><script>alert(1);</script></li> </ul>
scriptタグで囲まれた部分はJavaScriptのコードとして実行されることから、alert(1);
が実行され、画面上にメッセージボックスが出現する事となる。
今回、例としたページはコメントの内容をどこかに保存し、他のユーザにも見せていることから、攻撃者のブラウザだけでなく、このページを開いたすべてのユーザのブラウザ上でメッセージボックスが表示されることになる。
対策
htmlspecialchars()
関数を使用して表示する
または
テンプレートエンジンの機能を使用して表示する
ことでXSS攻撃への対策を行うことができる。
htmlspecialchars
XSS攻撃への対策は、<
や>
といったHTMLタグを構成しうる危険な文字を別の文字に置き換えることである。
PHPにはhtmlspecialchars()
という関数が用意されており、この関数を通すことで無害な文字へと変換することができる。
具体的には次のように使用する。
<ul> <?php foreach ($comments as $comment): ?> <li> <?= htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') ?> </li> <?php endforeach; ?> </ul> <form action="comment.php" method="post"> <input type="text" name="comment"> <button>コメントする</button> </form>
もしユーザが先程のscriptタグを含む文字列をコメントとして投稿した場合、次のように表示されることになる。
<ul> <li>&lt;script&gt;alert(1);&lt;/script&gt;</li> </ul>
<
や>
が<
や>
といった実体参照に置き換わっており、見かけ上同じ内容を表示しているが、タグとして認識されない無害な文字列へと置き換わっている。
このようにhtmlspecialchars()
を使用することで、無害な文字へとエスケープして表示させることができるようになる。
テンプレートエンジン
PHPは単独でもHTMLを生成することができるが、有志によって作成されている テンプレートエンジン を使用することで、より簡便にHTMLを生成できるようになる。
こういったテンプレートエンジンでは、最初から「安全にHTMLを生成する」機能が用意されていることがほとんどである。
あらかじめ用意された機能を使うことでXSS脆弱性を埋め込まない、安全なコードを生成することができるようになる。
例として最近人気が高まっているフレームワーク「Laravel」に付属しているテンプレートエンジン「Blade」での出力方法を見てみることにする。
Bladeでは{{
と}}
で囲むことにより、PHPの変数などをHTML上に出力する事ができるようになる(こういうのをmustache記法と言うらしい)。
<ul> @foreach($comments as $comment) <li>{{ $comment }}</li> @endforeach </ul>
このようにするだけで、安全に文字列を表示できるようになる。
基本的にテンプレートエンジンを使用している場合は、そのエンジンで提供されている機能を用いて表示することでXSSの脆弱性を埋め込まないようにできる。
誤った対策
htmlspecialchars()
は便利な関数であるが、使い方を誤った場合、XSS脆弱性を排除するという目的を達成できない可能性がある。
PHPの公式ページでhtmlspecialchars()
の仕様を確認すると、何もオプションを付けない状態だと、&
、"
、<
、>
のみをエスケープするようになっている。
ここで問題となるのが'
(シングルクォート)である。
シングルクォートは"
(ダブルクォート)の代わりに使用されることもあるため、シングルクォートをエスケープしないということは、適切にエスケープできていないということを意味する。
これは何もオプションを付けず
<?= htmlspecialchars($comment) ?>
のように実行した場合に引き起こされる。
「htmlspecialchars()
を使ってるから安心!」とはならないのである。
htmlspecialchars()
を実行するのは正しいが、何もオプションを指定しないことが間違っている。
htmlspecialchars()
を実行するときには次のようにオプションを指定すると上記の問題を防ぐことができる。
<?= htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') ?>
第2引数にENT_QUOTES
を指定することで、シングルクォートもエスケープ対象に含めることができるようになる。
もしほかのオプションと併用したい場合は、|
(パイプ)でつないで指定することができる。
第3引数には文字コードを明示しているが、ページの文字コードと異なる文字コードで出力した場合、適切にエスケープされなくなる問題があるためである。
ここではページの文字コードをUTF-8として想定しているが、もしページがShift_JISやEUC-JPである場合は、第3引数もそれに合わせて変更する必要がある。
なお、htmlspecialchars($hoge, ENT_QUOTES, 'UTF-8')
などと何度も書くのは長すぎるため、実務で使用する場合は、
function h($text) { return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); }
といった短い名前のヘルパ関数を用意しておくことが望ましいといえる。
htmlspecialchars()
を呼び出す場合には、必ずENT_QUOTESと文字コードを指定するよう、注意してほしい。
おわりに
クロスサイトスクリプティングの脆弱性はいくつかの攻撃手法に分けられる(参考:Wikipedia)。
いずれの手法にせよ、攻撃されてしまえば広い範囲で被害が発生することは容易に想像できる。
PHPにおいては「標準機能で防ぐことができる脆弱性」であるため、意識せずに「防ぐコード」が書けるようになることが望ましいと言える。