前回:やられアプリ BadTodo - 4.6 XSS パスワード変更ページ - demandosigno
やはり「安全なウェブサイトの作り方 - 1.5 クロスサイト・スクリプティング | 情報セキュリティ | IPA 独立行政法人 情報処理推進機構」の「対策について」が規範となります。
根本的解決(安全なウェブサイトの作り方より)
1.5.1 HTMLテキストの入力を許可しない場合の対策
* 5-(i) ウェブページに出力する全ての要素に対して、エスケープ処理を施す。
* 5-(ii) URLを出力するときは、「http://」や 「https://」で始まるURLのみを許可する。
* 5-(iii) 要素の内容を動的に生成しない。
* 5-(iv) スタイルシートを任意のサイトから取り込めるようにしない。
1.5.2 HTMLテキストの入力を許可する場合の対策
5-(vi) 入力されたHTMLテキストから構文解析木を作成し、スクリプトを含まない必要な要素のみを抽出する
1.5.3 全てのウェブアプリケーションに共通の対策
5-(viii) HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)を指定する。
安全なウェブサイトの作り方を読めばこの記事は必要ないくらいですが、BadTodoでの具体例に絞って書いていきます。
エスケープ処理
ブラウザは「<」のような特殊記号(メタ文字)をタグの開始と解釈します。一例としてHTMLで<s>タグを使うと打消し線が引かれます。打消し
これらの特殊記号はエスケープ処理することで<s>打消し</s>のようにただの文字列として扱われます。
変換前 → 変換後 & → & < → < > → > " → " ' → '
同様に<script>alert(1);</script>
のようなスクリプトを<script>alert(1);</script>
と変換することで無害化できます。
ウェブページに出力する全ての要素に対して、エスケープ処理を施してください。
エスケープ処理はデータの入力時ではなく、HTML を出力する時に行わなければならないとされています。これを徹底しておけば、PHP スクリプトの内部で二重にエスケープ処理を行ってしまったり、エスケープを忘れたりするような問題を回避しやすくなります。
PHP と Web アプリケーションのセキュリティについてのメモ
htmlspecialchars 関数
BadTodoのようにPHPでアプリケーションを開発する場合には、HTMLのエスケープ処理にはhtmlspecialchars関数が利用できます。PHP: htmlspecialchars - Manual
使用例:echo htmlspecialchars($string, ENT_QUOTES, "UTF-8");
第2引数がENT_QUOTESの場合、変換対象となる文字は上で挙げた5つです(&, <, >, ", ')
例としてTodo「検索」時の挙動で検証します。
通常通り
test
で検索したときのリクエスト
https://todo.example.jp/todolist.php?rnd=64e6b73b4312a&key=test
のHTMLレスポンスの当該部分は次の通り。
<form action='/todolist.php' method='get'> <input type='hidden' name='rnd' value='64e6b73d3c321'> <input type="text" name="key" value="test"> ←ここ <input type="submit" value="検索"> <label for="like-search">あいまい検索</label> <input type="checkbox" name="like-search" value="1" id="like-search" > </form>
検索文字列を"><script>alert(1);</script>
としたときは
<form action='/todolist.php' method='get'> <input type='hidden' name='rnd' value='64e6b89dcc2c7'> <input type="text" name="key" value=""><script>alert(1);</script>"> ←ここ <input type="submit" value="検索"> <label for="like-search">あいまい検索</label> <input type="checkbox" name="like-search" value="1" id="like-search" > </form>
<script>alert(1);</script>
がHTMLにそのまま出力されているためタグと解釈されXSSが発動します。
ソースコード上では次のようになっています。(todolist.php)
<?php $app->form($_SERVER['PHP_SELF'], false); ?> <input type="text" name="key" value="<?php echo $key; ?>"> ←ここ <input type="submit" value="検索"> <label for="like-search">あいまい検索</label> <input type="checkbox" name="like-search" value="1" id="like-search" <?php echo $like_search ? ' checked' : ''?>> </form>
ここの<?php echo $key; ?>
の部分をhtmlspecialcharsを使って修正します。
<?php $app->form($_SERVER['PHP_SELF'], false); ?> <!--<input type="text" name="key" value="<?php echo $key; ?>">--> <input type="text" name="key" value="<?php echo htmlspecialchars($key, ENT_QUOTES, "UTF-8"); ?>"> ←ここ <input type="submit" value="検索"> <label for="like-search">あいまい検索</label> <input type="checkbox" name="like-search" value="1" id="like-search" <?php echo $like_search ? ' checked' : ''?>> </form>
するとXSSは発動しませんでした。
<form action='/todolist.php' method='get'> <input type='hidden' name='rnd' value='64e6bde036efa'> <input type="text" name="key" value=""><script>alert(1);</script>"> ←ここ <input type="submit" value="検索"> <label for="like-search">あいまい検索</label> <input type="checkbox" name="like-search" value="1" id="like-search" > </form>
レスポンスの当該箇所が"><script>alert(1);</script>
とエスケープ処理されたためです。
common.php にて
function e($s) { echo htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
と定義されているため、
<input type="text" name="key" value="<?php e($key); ?>">
とすると少し楽です。
引き続き保険的対応策を書いていきます。