« MySQL の order by 〜 limit を高速化する方法 | メイン | MySQL Conference & Expo 2009 で Q4M の話をします »

2008年12月16日

Text::MicroTemplate - テンプレートエンジンのセキュリティと利便性

 先月開催された Shibuya.pm #10 でプレゼンテーションがあった MENTANanoA では、Mojo 由来のテンプレートエンジンを拡張して使用してきたのですが、Perl モジュールとして独立させるべきだよね、ということになり、このたび Text::MicroTemplate として CPAN にアップロードしました。

 そのことを告知するとともに、作業の過程で興味深く感じた、テンプレートエンジンのセキュリティと利便性に関する話題をブログに書いておこうと思います。

テンプレートエンジンのエスケープ機能の歴史

 初期のテンプレートエンジンは、手動でエスケープを指定する必要がありました。この点については XSS の温床になるとして、「デフォルトでエスケープされるように作られていたらよかったのに」(高木浩光@自宅の日記) という批判があり、新しいテンプレートエンジンでは、テンプレートのタグ内で明示的に指定しない限り、渡された文字列を HTML エスケープするものが増えていると思います。

 ただ、埋め込みタグによりエスケープ手法を決定する方式では、HTML タグが二重にエスケープされてしまったり、あるいはそれを回避しようとして XSS が発生するなどの問題が残ってしまいます。この問題を回避するために、Django 等のテンプレートエンジンでは、通常の文字列型とは別に「HTML エンコードされた文字列型」を用意し、置換文字列の型を元にエスケープするかしないかを決定するようになっています (テンプレート内の位置ではなく、実行時の型情報にもとづいてエスケープを行う、と説明すべきかもしれません)。

 このような「自動エスケープ」手法を使うことで、ウェブアプリケーション開発者は、エスケープの必要性を (ほぼ) 気にすることなく、作業を行うことができるようになります。Text::MicroTemplate でも、この考え方に基づき自動エスケープ機能を実装しました。

スニペットの問題

 一方で、実際のウェブアプリケーションにおいては、テンプレートエンジンだけが HTML を生成しているわけではありません。パン屑リストのような小規模な HTML スニペットやちょっとしたリンクタグを生成するために、いちいちファイルベースのテンプレートエンジンを使うのは面倒なので、プログラマは、ついつい自分で文字列連結をしてしまいがちです。が、これが XSS の温床になることは、言うまでもありません。

 Perl の代表的なテンプレートエンジンである Template-Toolkit には、そういった状況下でテンプレートエンジンが使用できるよう、(ファイルベースではなく) 文字列ベースのテンプレート機能 (String::TT) があります (一方で Template-Toolkit には、原則手動エスケープだという問題があります)。

 Text::MicroTemplate では String::TT 同様の、スニペットを生成するための関数 build_mt を提供しており、以下のように書くことができます。

$oneline_message = render_mt(
     '<?= $_[0] ?> さん、お買い上げありがとうございます',
     $username,
);

 この例では、ユーザー名が HTML エスケープされる一方、$online_message は「HTML エンコードされた文字列型」になるので、そのままテンプレートエンジンに渡しても二重エスケープは発生しません。

最後に

 自分が最近、あまり興味をもってこなかった分野でないからかもしれませんが、今回テンプレートエンジンをいじってみて、いろいろ勉強になりました。上記の要素は、テンプレートエンジンを実装する際の、あるいは評価する際の尺度にもなると思うので、参考にしていただければ幸いです。また、欠落している要素があれば、指摘していただければありがたく存じます。

 本当は Text::MicroTemplate のチュートリアルも書くべきななんだけど力つきたのでこれでおしまい...

12月17日追記:

ところで、HTML エンコードされた文字列型を使う必要のある場面というとどんなところだろうか はてなブックマーク - HiromitsuTakagiのブックマーク

 たとえば以下のようなケースはどうでしょうか。

 一般に、ウェブアプリケーションの View - Controller 分離では、コントローラ側で必要な情報を全て準備してから、テンプレートのレンダリングを行うと思います。このような環境下で、例えば2ペイン構成のサイドバーに様々なガジェットを表示することを考えてみます。この場合、ガジェットを生成する手法は、

  1. ガジェット構築関数を呼び出すと HTML が返ってくるので、メイン画面のテンプレート内で、それを差し込む
  2. ガジェット構築関数を呼び出すと、テンプレートファイル名とテンプレートへの引数が返ってくるので、メイン画面のテンプレート内で、ガジェットが指定したテンプレートファイルを指定された引数を渡しつつインクルードする
のいずれかになると思います。前者が直感的であるのに対して、後者は (自動エスケープに対応していないテンプレートエンジンでの) 二重エスケープの可能性こそないものの、複雑です。

投稿者 kazuho : 2008年12月16日 20:59 このエントリーを含むはてなブックマーク このエントリーを含むはてなブックマーク

トラックバック

このエントリーのトラックバックURL:
http://labs.cybozu.co.jp/cgi-bin/mt-admin/mt-tbp.cgi/2097