第32回 PHPセキュリティ月間
今回もMOPS関連の話題です。MOPSではPHP関連のセキュリティ製品やセキュリティ知識の論文を募集し、
- MOPS Submission 02 – Context-aware HTML escaping
 - http://
www. php-security. org/ 2010/ 05/ 05/ mops-submission-02-context-aware-html-escaping/ index. html  
このテンプレートエンジンはNette Latteと呼ばれています。このテンプレートエンジンを独立して使用することもできますが、
コピーライトを見ると2004年からとなっており、
コンテクストを検出するHTMLテンプレートエンジンとは?
JavaScriptインジェクションを防ぐようHTMLを記述することは簡単ではありません。HTMLの中に記述される情報はテキストやHTMLでマークアップされたテキストだけではありません。タグの属性、
変数を記載する場所を自動的に検出してエスケープするテンプレートエンジンは
Nette Latteが検出するコンテクスト
Nette Latteは以下のコンテクストを検出して適切なエスケープ処理を行います。
- HTMLテキスト
 - "または'で囲まれたHTML属性
 - <script>または<style>で囲まれたCDATAのセクション
 - HTML属性のstyleおよびonclickなどのイベンドハンドラ
 - HTMLコメント
 
URLを属性値とする属性
Nette Latteの利用例
どのように利用するのかは利用例を見るとすぐ分かります。
<script type="text/javascript">
var userId = {$userId};
</script>;
<p style="color: {$color};" title="{$title}">;
<a href="" onclick="return !confirm({$message});">{$desc}</a>;
</p>
<!-- Executed in: {$time} s -->;
Nette Latteのテンプレートでは各変数をそのまま出力しているように記述しますが、
Nette Latteには自動的なエスケープを停止するオプションはありません。その代わりに {!$var} と変数の前に ! を追加します。
Nette Latteのマクロ
ほかのテンプレートエンジンと同様にさまざまなマクロをサポートしています。
{$variable} | テンプレート変数の出力 | 
{!$variable} | テンプレート変数をエスケープなしで出力 | 
{*text comment*} | テンプレート用のコメント | 
{plink ...} | presenterのリンク | 
{link ...} | urlの生成 | 
{if ?} ... {elseif ?} ... {/if} | <?php if (?): ?> ... <?php elseif (?): ?> ... <?php endif ?> | 
{foreach ?} ... {/foreach} | <?php foreach (?): ?> ... <?php endforeach ?> | 
{for ?} ... {/for} | <?php for (?): ?> ... <?php endfor ?> | 
{while ?} ... {/while} | <?php while (?): ?> ... <?php endwhile ?> | 
{include dir/ | テンプレートのインクルード | 
{var foo => value} | テンプレート変数宣言 | 
{default foo => value} | デフォルト値の設定 | 
{control loginForm} | ログインフォーム | 
{dump $variable} | 変数のダンプ | 
これだけでも十分な機能を持っていますが、
{=expression} | <?php echo htmlSpecialChars(expression) ?> | 
{!=expression} | <?php echo expression ?> | 
{?expression} | PHPコードを評価 | 
{_expression} | トランスレーション | 
{!_expression} | エスケープ無しのトランスレーション | 
{ifCurrent} | {if}がアクティブリンクの場合の特別なif | 
{include 'dir/ | テンプレートの読み込み | 
{cache ?} ... {/cache} | キャッシュチェック | 
{snippet ?} ... {/snippet} | 制御スニペット | 
{attr ?} | HTMLタグ属性の登録 | 
{capture $var} ... {/capture} | 出力バッファリングを行い$varに保存 | 
{block | texy} ... {/block} | texy ブロック | 
{widget ...} | コンポーネントをレンダリング | 
{control ...} | widgetのエイリアス | 
{contentType ?} | HTTPヘッダのContent-Typeを送信 | 
{debugbreak} | ブレークポイントの挿入 | 
Nette Latteの試用
Nete Latteが含まれているNette Frameworkは3つバージョンが用意されています。
- PHP 5.
2用でクラス名にプレフィックス有り  - PHP 5.
2用でクラス名にプレフィックス無し  - PHP 5.
3用で名前空間を使用  
執筆時点での最新版は1.
Nette Latte テンプレートを使ってみる
MOPSの文書ではNette Latteテンプレートは独立して利用できると記述されていましたが、
  1 <?php
  2 require_once __DIR__ . '/../../Nette/loader.php';
  3 
  4 use Nette\Forms\Form, Nette\Debug, Nette\Templates, Nette\Templates\Filters;
  5 
  6 Debug::enable();
  7 
  8 // absolute filesystem path to the web root
  9 const WWW_DIR=__DIR__;
 10 
 11 // absolute filesystem path to the application root
 12 const APP_DIR=__DIR__;
 13 
 14 // absolute filesystem path to the libraries
 15 define('LIBS_DIR', __DIR__ . '/../../3rdParty');
 16 
 17 // テンプレートオブジェクトの作成
 18 $template = new Nette\Templates\Template;
 19 // Nette Latteフィルタの登録
 20 $template->registerFilter(new Nette\Templates\LatteFilter);
 21 $template->setFile( 'template.phtml' );
 22 // テンプレート変数の登録
 23 $template->userId = 1234;
 24 $template->arrayVar = array('a'=>1, 'b'=>1, 'c'=>3);
 25 $template->stringVar = 'JS string <>"\'%';
 26 $template->color = 'expression(); red"';
 27 $template->title = 'ABC';
 28 $template->url = 'http://www.es-i.jp/';
 29 $template->message = 'Are you sure? "<>%';
 30 $template->desc = '<Click Here>';
 31 $template->time = '1234 -->';
 32 // テンプレートを出力
 33 echo $template;
 34 
テンプレートファイルは先程のサンプルテンプレートに多少付け加えたものを用意しました。
  1 <html>
  2 <head>
  3 </head>
  4 <body>
  5 <script type="text/javascript">
  6 var userId = {$userId};
  7 var arrayVar = {$arrayVar};
  8 var stringVar = {$stringVar};
  9 </script>;
 10 <p style="color: {$color};" title="{$title}">;
 11 <a href="{$url}" onclick="return !confirm({$message});">{$desc}</a>;
 12 </p>
 13 <!-- Executed in: {$time} s -->;
 14 </body>
Nette Latteのマクロとは{macro名}のように{}で囲まれた部分がマクロとしてテンプレートエンジンで置換されます。
{link}マクロと使おうとしたところエラーが発生したため、
{link コントローラ名:アクション名, パラメータ}
となっているので当然と言えば当然です。フレームワークを使っていない場合、
<html>
<head>
</head>
<body>
<script type="text/javascript">
var userId = 1234;
var arrayVar = {"a":1,"b":1,"c":3};
var stringVar = "JS string <>\"'%";
</script>;
<p style="color: expression\(\)\;\ red\";" title="ABC">;
<a href="http://www.es-i.jp/" onclick="return !confirm("Are you sure? \"<>%");"><Click Here></a>;
</p>
<!-- Executed in: 1234 --><!--> s -->;
</body>
いくつかエスケープすべき文字を入れてみましたが、
まとめ
Nette Latteはデフォルトでコンテクストに合わせたスケープ処理を行い、
ほかのフレームワークと組み合わせて使用する場合、
