LaravelのBladeで{{ csrf_field() }}が通る理由

なにかの間違えで、{{ csrf_field() }}と書いていたんだけどそれが通るということがあって、{{ ... }}の仕様どうなってるのかなと思い確認した。

{{ ... }}は紆余曲折を経て、<?php echo e(...) ?>に変換されていた。e()はPHPerなら予想がつくだろう通りhtmlspecialchars()のラッパで、かつHtmlableインターフェイスを実装したクラスのインスタンスエスケープせずに通す、という仕様だった。まあ、よくあるやつだ。

なお調査ルートは以下の通り。

  1. {{, }}$BladeCompiler::contentTagsで定義されている。
  2. $BladeCompiler::contentTagsCompilesEchos::compileRegularEchos()で使用されている。
  3. compileRegularEchos(){{ ... }}から<?php echo e(...) ?>への変換が実装されている。
  4. ただし1, 2の関係が多少ややこしくて、$BladeCompiler::compilersBladeCompiler::parseToken()経由でCompilesEchos::compileEchos()が呼び出されて、そこからさらにcompileRegularEchos()が、という流れになっている。

CompilesEchosなどはBladeCompilerから5.4で分割されたようだが、もうちょっとリファクタリングして欲しい感じある。

あと、Htmlable周りはドキュメントに書かれていないが、これを知らないことにより{{ ... }}の使い方を間違えた場合に(多分)XSSしうるので、ドキュメントにちゃんと書いて欲しいなあ。{{ ... }}は常にエスケープするわけではないあたりを。

エスケープしない可能性があるのはHtmlableに関してだけではない。調査の中でBlade::setEchoFormat()という危ないメソッドの存在も知った。これを適当に書き変えちゃうと、変換先がe(...)に限らなくなってしまうので、最悪エスケープをなかったことにできたりもして危ない。まあ普通書き換えないだろうけど、もうちょっとなんとかならなかったのか。

また関連して{{{ ... }}}というのを見つけた。元々はこれと、{!! ... !!}だけだったのが、5から{{ ... }}が追加されたようだ。setEchoFormat()は見ないので安定しているかと思いきやe()を使ってるのでHtmlableは通すし、そもそもドキュメントからも消えているので多分使わない方がいい。

なお当初の疑問であった{{ csrf_field() }}についてだが、とりあえずいろいろ考えると{!! csrf_field() !!}などするのではなく{{ csrf_field() }}にするのが妥当そうである。が、そもそも5.6以降は@csrf, @methodなんてのができており、こちらを使うのがより妥当そうである。