WEBブラウザではブラウザ上でプログラミング言語が動作するようになっていて、この仕組が自動的にユーザーの行動を補完してくれることでユーザーは様々な便利な機能を使うことができます。

しかし、これは裏を返せば悪用されるとユーザーの意図に反した処理がユーザーの知らない内に行われてしまうことを意味します。

WEBサイト制作者は、ユーザーがこういった不利益を被らないように、最新の注意を払ってWEBサイトを制作するひつようがあります。

※この記事は執筆・公開から3年以上経過しています。記事の情報が古くなっている場合がありますのでご注意ください。

公開日時:2016/02/20 20:06
最終更新:2019/12/10 02:22

XSS対策~ 365日の紙PHP(11日目)

昨日までで、安全なセッションの利用について学びました。

その対策は、サーバー側での対策でした。

でも、よく考えてみてください。昨日の攻撃のそもそもの原因はどこにあったのでしょう?

それは、被害者のセションIDが攻撃者のセッションIDとして固定化されてしまったことです。どこで固定化されたでしょう?

ログインフォームのID欄に、

"><script>document.cookie="PHPSESSID=コピーした文字列"</script><span class="

という文字列を入力したためにセッションIDが固定化されてしまっていました。

この固定化をさせなければ問題は起こらなかったのです。

なぜ固定化されたのでしょう?

それは、

document.cookie="PHPSESSID=コピーした文字列"

という命令がプログラムコードとして実行されてしまったのが原因です。

これはJavaScriptのコードです。cookieに値を設定していることはわかりますね?

このように、ユーザーが何かの文字列を入力したらそれがプログラムコードとして認識されて実行されてしまうのではとても困ります。

ですから、そういうことがないようにプログラム開発者は対策しておく必要があります。

どこが問題なのか考える

どうしてユーザーが入力した文字列がプログラムコードとして実行されてしまったのでしょう?

それは

<script>...</script>

という記述のせいです。このように<script/>タグで囲まれた文字列は、ブラウザによってプログラムコードであると認識され、埋め込みプログラムコードの実行を拒否する設定になっていない限り、ブラウザが自動的に実行してしまうのです。

攻撃者はこのブラウザの特性を悪用しました。

だったら、このタグを無効にしてしまえばよいのです。

タグを無効化しよう!

HTMLでは、<foo>***</foo>というタグを用いて文書をマークアップして記述します。

ご覧のように、<>で囲まれた文字列がタグとして意味付けされます。これは、どんなタグでも同じになっています。

ですから、< と > がなければ、それはタグとして意味を成さなくなります。

<script>

はタグですが、

#script#

は、タグではないのです。この特性を利用します。

タグを実体参照に置き換える

PHPには、タグと認識してしまう文字 < > 等を自動で別の文字列に置き換えてくれる関数が存在します。

htmlspecialchars()

この関数を使うと、特定の文字を実体参照に置き換えてくれます。

実体参照というのは、文字を直接指定するのではなく手続きを用いて表記する方法です。

例えば < は直接表記すると < ですが、この文字は &lt; という表記を用いることで参照出来るようになっています。同様に >&gt; です。

index.phpに

echo "&lt; &gt;";

というコードを書いてアクセスしてみましょう。

< >

が表示されたはずです。次は、

echo htmlspecialchars("< >");

を試してみましょう。ブラウザを右クリックしてソースを確認してみると、 < > が < > として出力されていることが確認できると思います。

こうするとこれはタグとして認識されなくなります。

つまり、このような実体参照への自動変換を行ってしまえば良いわけです。

プログラムの変更

プログラムの変更は簡単です。

index.php の入力されたIDの復元を行っているコードを修正します。

    <span class="login-label">ID</span> : <input type="text" id="id-field" name="user_id" value="<?php if(isset($_POST["user_id"])){ echo htmlspecialchars($_POST["user_id"]); } ?>"><br>

どうでしょう?  ID欄に

<script>

と入力して送信するとその文字列がそのまま表示される事が確認できるでしょう。ソースコードを確認すると &lt;script&gt; への置き換えが行われているのが確認出来るはずです。<script>alert(0);</script>が実行されない事を確認して下さい。

このように、悪意ある文字列をプログラムコードとして実行させない処理はとても簡単に導入できるのです。

なお、

htmlspecialchars()

を確認いただくと分かるのですが、htmlspecialchars() には第2引数、第3引数が存在します。

第2引数はどのような実体参照への変換を行うかを指定する引数で、通常は ENT_QUOTES(シングルクオートとダブルクオートを共に変換します)が利用されます。用途に合わせて変更してください。

第3引数は文字のエンコード指定です。現在のWEBでは文字コードUTF-8が標準で利用されていますが、別の文字コードを利用する場合はここで指定します。この第3引数は必ず指定するのが推奨されています。

ということで、通常の利用では

htmlspecialchars($_POST["user_id"], ENT_QUOTES, "UTF-8"); 
<span class="login-label">ID</span> : <input type="text" id="id-field" name="user_id" value="<?php if(isset($_POST["user_id"])){ echo htmlspecialchars($_POST["user_id"], ENT_QUOTES, "UTF-8"); } ?>"><br>

のように利用することになるかと思います。

また、login.php でもユーザーからの入力を出力する箇所

<p>ログイン処理が成功しました。ようこそ<?php echo $_SESSION["user_id"];  ?>さん。</p>

がありますので、ここも同様に変更しておきましょう。

<p>ログイン処理が成功しました。ようこそ<?php echo htmlspecialchars($_SESSION["user_id"], ENT_QUOTES, "UTF-8");  ?>さん。</p>

ユーザーから入力された文字列を出力する場所では、内容の事など考えずにとにかくhtmlspecialchars() 関数を通して出力してしまえば、怪しいプログラムコードの実行を阻止する事ができます。

これでやっと、安全なログインフォームが完成しました。

お疲れ様でした。ログインフォームはこれでおしまいです。次はそろそろ、ソースコードのバージョン管理について触れたいと思います。

バージョン管理を使うと、安心してプログラムコードの修正が行えるようになります。

記事リンク