PHPのイタい入門書を読んでAjaxのXSSについて検討した(1)

 このエントリでは、あるPHPの入門書を題材として、Ajaxアプリケーションの脆弱性について検討します。全3回となる予定です。

このエントリを書いたきっかけ

 twitterからタレコミをちょうだいして、作りながら基礎から学ぶPHPによるWebアプリケーション入門XAMPP/jQuery/HTML5で作るイマドキのWeという本を読みました。所感は以下の通りです。

 今時この水準はないわーと思いました。以前私がレビューした本は、最低でも、XSSSQLインジェクションの二大脆弱性については一応の配慮があり、しかし漏れがあったり、他の脆弱性があったりという箇所を指摘していました。それに対して、本書は、セキュリティへの配慮は、見事なほどにありません。本書の索引には「セキュリティ」という項目はありますが、それはXAMPPのセキュリティ設定に関するもので、アプリケーションの脆弱性についての説明は一切ありません。

 タレコミ氏からはレビューをお願いされたのですが、あまりに基本的な対策漏れを淡々と指摘しても、読者にとって有益な情報を提供できないと思いました。その一方で、本書ならではの視点が提供できることに気がつきました。それは、本書が基本的に静的HTMLとAjaxにより動的コンテンツを提供している点です。
 AjaxアプリケーションのXSSは、拙著「体系的に学ぶ 安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践」では紹介していません。同書執筆時点で、AjaxXSSに対してどう対策するべきなのか、十分整理できていなかったためです。そこで、議論のきっかけとして、本書のAjaxを用いたアプリケーションのXSSと対策案を紹介したいと思います。
 本書のアプリケーションは下図(本書P20の図1-23から引用)に示す構成です。

 まず、(1)でHTML5JavaScriptによる画面がブラウザに表示されます。画面からは、XMLHttpRequest(jQeuryによる実装)によりPHP記述のAPIを呼び出し(2)、MySQLのクエリー(3, 4)の結果を返します(5)。受け取った結果は、DOMを用いて表示されます(jQueryによる実装)。基本的なAjaxアプリケーションの構成を押さえたものであり、意欲的な構成ですね。
 今回は、まず基本的な画面表示のXSSを紹介します。

画面表示箇所(DOM)のXSS対策ができている例

 狭義のXSSは、最後のDOMによる表示箇所のXSSということになりますが、意外なことに「セキュリティを全く意識していない」割にはこの部分のXSSは比較的少ないのです。それは、jQeuryのtextメソッドを用いているために、自動的にエスケープされているからです*1。その例をまず示しましょう。以下は、「Xmasパーティ予約情報参照」という画面の表示処理の部分です(本書P176リスト6-4より引用)。

function show(out){
  var res = out.split("<i>");
  $("#name").text(res[0]);
  $("#org").text(res[1]);
  $("#addr").text(res[2]);
  $("#tel").text(res[3]);
  $("#mail").text(res[4]);
  $("#course").text(res[5]);
  $("#nums").text(res[6]);
  $("#stat").text(res[7]);
}

 このスクリプトの実行例を下図に示します。textメソッドにより、小なり記号「<」などが適切にエスケープされ、XSS脆弱性はありません(下図)。

画面表示箇所(DOM)のXSS対策ができていない例

 一方、画面表示箇所のXSS対策ができていない場合もあります。それは、table要素の組み立てなど、HTML断片を文字列連結で組み立てている箇所です。例を引用します(P209リスト7-4)。

var head = "<table border='1'><tr>";
head+="<th>ID</th>";
head+="<th>代表者名</th>";
head+="<th>所属組織</th>";
head+="<th>電話番号</th>";
head+="<th>コース</th>";
head+="<th>人数</th>";
head+="<th>登録日時</th>";
head+="<th>更新日時</th>";
$(function(){ 
    $("#rev").click(function(){
        $.get("revallxmas2.php", {}, show);
    });
});
function show(out){
    var body = "";
    var recs = out.split("<r>");
    for(var i = 0; i <recs.length; i++){
        body += "<tr>";
        var flds = recs[i].split("<i>");
        for(var j = 0; j <flds.length; j++){
            if(j!=5){
                body += "<td>" + flds[j] + "</td>";                 // ← ココ
            }else{
                body += "<td align='right'>" + flds[j] + "</td>";   // ← ココ
            }
        }
        body += "</tr>";
    }
    body += "</table>";
    $("#show").html(head+body);
}

 「ココ」とコメントしたところにXSS脆弱性があります。組み立てられたHTML断片は最終的にhtmlメソッドで描画されますが、flds[j]はHTMLエスケープされていないことがXSS脆弱性の原因となっています。この箇所では、「<td>」というタグとflds[j]という生データを文字列連結していますが、この時点でXSSがあることは確実ですね。
 実際にXSSが起こっている画面例を以下に示します。alertの表示の他、打ち消し線の表示で、XSS脆弱性があることがわかります。

 次にこのスクリプトXSS対策してみましょう。表示の際にHTMLエスケープすればよいわけですが、サーバー(PHP)側でエスケープする方法と、JavaScript側でエスケープする方法があります。ここでは、表示の際にエスケープするという原則に従って、JavaScriptエスケープすることにします。この問題は最後でもう一度議論します。
 元のスクリプトjQueryの機能を十分使っていないように思われるので、もう少しjQueryを活用する形で書いてみました。jQueryの使い方などでツッコミがあれば歓迎します。

function show(out){
    var table = $("<table border='1'><tr><th>ID</th><th>代表者名</th><th>所属組織</th><th>電話番号</th><th>コース</th><th>人数</th><th>登録日時</th><th>更新日時</th>");
    var recs = out.split("<r>");
    for(var i = 0; i < recs.length; i++) {
        var flds = recs[i].split("<i>");
        var row = $("<tr>");
        for(var j = 0; j < flds.length; j++) {
            if(j != 5) {
                col = $("<td>").text(flds[j]);
            } else {
                col = $("<td align='right'>").text(flds[j]);
            }
            row.append(col);
        }
        table.append(row);
    }
    $("#show").empty().append(table);
}

 ご覧のように、table、行(変数row)、列(変数col)を用意して、appendメソッドで付け足していくという方法をとりました。テキストはtextメソッドで埋め込んでいるので、jQuery側でエスケープしてくれます。
 同じデータで、対策版では以下のように表示されます。小なり記号「<」などがエスケープされている様子がわかります。

HTMLエスケープをどこで行うか

 通常のWebアプリケーションの場合、HTMLで表示するパラメータのエスケープはサーバー側で行うしかありませんが、Ajaxアプリの場合、以下の選択肢があります。

 原則論から言えば、サーバーから生データを送るのが正しいように思います。Ajaxで受け取ったデータは表示するだけでなく、さまざまなデータ処理に活用する可能性があり、その場合は生データが便利です。また、表示の直前にエスケープするという原則にかなうからです。
 この問題については、id:malaさんの以下のブログエントリも参考になります(Ajaxがテーマではありませんが共通する話題です)。

参考:HTMLのscriptタグ内に出力されるJavaScriptのエスケープ処理に起因するXSSがとても多い件について - 金利0無利息キャッシング – キャッシングできます - subtech

まとめ

 Ajaxアプリケーションの単純なXSSについて説明しました。Ajaxを活用したアプリケーションであっても、HTMLとして出力するデータのエスケープは必要というあたりまえのことですね。
 次回は、Ajaxとして受け取るデータの問題(XSSではなく、evalインジェクション等)について説明します。


[PR]

*1:id:mala さんから指摘をいただき、textメソッドは自動エスケープというより、タグが生成されないと説明した方が良いとのことでした。確かにその通りだと思います。また、以下の説明も同様です。