ぐらめぬ・ぜぷつぇんのはてダ(2007 to 2011)

2007年~2011年ごろまで はてなダイアリー に書いてた記事を引っ越してきました。

"smarty:nodefaults"の再調整、およびprofileモジュールのミニ修正

まず、profileモジュールのGuard.phpに古いtxファイルへのrequireが残っていたので除去。

あと全然関係ないけど、最近YouTubeとニコ動経由でALI PROJECT - Wikipediaにはまりつつある。東方名曲選がらみでたまたまALI PROJECTの詰め合わせ1時間パックをGETしたのだが、そこにNOIRの「コッペリアの柩」が収録されていて、「あ・・・これかぁ!」と今さら気づき、さらにその直後、脳噛ネウロのOPのMADで使われていた「人生美味礼賛」が出てきてのけぞる。
やられた。アニメのOPとかEDも沢山こなしてるみたいなので、アニメとか好きな人には相性が良いのかも知れない。というか元々「人間椅子」とか「筋肉少女帯」とか「ZABADAK」とかが好きだったりする人間だからなぁ・・・。

で、smarty:nodefaultsの調整の件ですが、これはすごい間の抜けていた事に、今日までinputやtextareaについて値の部分はHTMLエスケープしなくても大丈夫だと、どこをどう勘違いすればそう思いこめるのか今となっては甚だ謎で・・・。

とにかく、思いっきり勘違いしていた部分があった為、多数の入力フィールドでscriptをインジェクションできてしまっていたという話です。

というわけで、HTMLテンプレートを中心にあちこち直しました。ああ、面倒くさかった。

以下、何をどう勘違いしていたのか。

勘違いその1:inputタグ

えっと、「テキストボックスはvalueに指定した値をそのまま表示するのだからエスケープしなくて大丈夫だろう」と勘違いしてました。

<?php $a = "aaaa\">&'<bbb"; ?>
<input type="text" name="..." value="<?php echo $a; ?>" />

↑みたいなケースで、$aはエスケープ不要と勘違いしてました。

・・・んなわけないだろうがぁあ!!!!
だったらどーやって、その結果となる

<input type="text" name="..." value="aaaa">&'<bbb" />

において、aaaからbbbまでがvalue句だと見当つけられるんだよ!?という話です。

これ、試せば一発で分かるんですよね。
htmlspecialchars()してあげれば表示上もちゃんとaaaからbbbまでが入力欄に出てきますし、POSTされる値もちゃんと元の「aaaa">&'

勘違いその2:textareaタグ

ハイ、同上。

<?php $b = "aaa</textarea>bbb"; ?>
<textarea><?php echo $b; ?></textarea>

↑でhtmlspecialcharsが不要と思ってました。

・・・自分の頭が信じられなくなる瞬間ってこういうのを言うんですよね。
一体、上記の出力結果となる

<textarea>aaa</textarea>bbb</textarea>

をどうブラウザが解釈するモノだと思っていたのか、昨日までの自分に詰問したい所です。

勘違い・・・というかSmartyで嵌ったところ。

ここを参考に、下記のようなmodifierをdefault_modifierに指定しています。
sfSmartyViewPluginあれこれ - Do You PHP はてな

function smarty_modifier_yb_escape($string, $esc_type = 'html', $char_set = 'UTF-8')
{
    switch (gettype($string)) {
    case 'string':
        return smarty_modifier_escape($string, $esc_type, $char_set);
    default:
        return $string;
    }
}

これを使えば、inputタグの部分などは

<input type="text" name="..." value="<{$aaa}>" />

とそのまま書くだけでOKなので非常に助かります。ちなみに、上記コードでちゃんとENT_QUOTE付きでhtmlspecialchars()を呼んでくれています。

ところが。

YakiBikiでは、Smartyプラグインの中でさらにSmartyテンプレートを処理させる場合が結構あります。データ型毎に入力フォームがまるで異なる場合は、一種のfactory + template-methodパターンの変種にして、template-methodにあたる部分をSmartyのplugin経由で呼び出しています。とにかく、assignされた値をSmartyのplugin経由で渡して、さらにそれをフォームの値として表示させる経由があります。

で、pluginの関数に入った時点で既に、連想配列の中身まで一度エスケープされてました。
なのでplugin中からさらにテンプレートを出力させる場合は

<input type="text" name="..." value="<{$aaa|smarty:nodefaults}>" />

としておかないと、二重にエスケープされてしまうと言う罠。やられた・・・。

つまり単純に "smarty:nodefaults" を削るだけじゃ駄目で、plugin経由で出力されているのかも確認しないと不安。結局smarty:nodefaults使っている箇所を「全部」、実際に「", >, <, ', &」を入力してみて正常に表示・保存できるか確認する羽目に。

・・・ここら辺が、エスケープ漏れが発生する一因かもしれない・・・。テンプレートやレンダラで二重にエスケープが走ったりする箇所とか、Smartyの場合はplugin, symfonyとかの場合はpartialかな?あたりが入れ子になっちゃうあたりで、少し怪しくなる。

蛇足:aタグのhref属性でのHTMLエスケープ

すごい極端な例として、

<?php $a = "http://yb-test/?id=\"<>&'&";
<a href="<?php echo $a; ?>">

みたいな事をやって、$aをhtmlspecialcharsでエスケープしてみる。
→こうなる。

<a href="http://yb-test/?id=&quot;&lt;&gt;&amp;&#039;&amp;">

さて、これをFirefoxIE7でそれぞれ実際にクリックしてみると・・・

Firefox: 
http://yb-test/?id=%22%3C%3E&'&

IE7:
http://yb-test/?id="<>&'&

実際にApacheアクセスログを見ると・・・

Firefox : 
127.0.0.1 - - [20/Oct/2008:23:57:34 +0900] "GET /?id=%22%3C%3E&'& HTTP/1.1" 200 ...

IE7: 
127.0.0.1 - - [20/Oct/2008:23:57:57 +0900] "GET /?id=\"<>&'& HTTP/1.1" 200 ...

・・・IEの方は特にエンコードされずに送信しているみたい。とはいえ、HTMLエスケープしたhref属性でも、ちゃんと元に戻してから送信してくれているようで一安心でした。(モバイル系の場合がまだ不安ですが。)