【Laravel TinyMCE導入】Textareaで入力したHTMLソースを画面にそのまま出力する方法&注意すべきXSS対策について
Laravelにテキストエディタを導入しよう!ということで、『TinyMCE』を使うことにしました。
WordPressに昔から使われていたビジュアルエディタと同様のもので、現在は『Gutenberg』と呼ばれるブロックエディタが標準仕様になっています。
ブロックエディタは、文章の順番を簡単に入れ替えることができるという編集能力が強みです。
しかし、ブロックエディタは使いづらいという声も沢山上がっていて、昔のビジュアルエディタへ戻すためのプラグインがあるほどです。
WordPressやnoteなどのWeb系ブログサービスのほとんどは、ブロックエディタの仕様になっていますね。
確かに使い勝手が良いかもしれませんが、カスタマイズ性はTinyMCEの方が良いかと思います。
今回は、Laravelへの『TinyMCE』導入後に試行錯誤したポイントをまとめます。
{{ $変数名 }}
ではなく{!! $変数名 !!}
or<?php echo $変数名; ?>
or<?php html_entity_decode($変数名); ?>
を直接使うと危険
Laravelの記法の一つとして、{{ $変数名 }}
があります。
これは、例えばcontrollerからBladeへ変数を受け渡して表示する場合などでよく使われている記述です。
{{ }}
で囲んであげることによって、自動的にXSS対策が行われています。
とても便利ですね!Vue.jsなどと{{ }}
が衝突するので色々工数が増えますが…(汗)
しかしながら、ブログサービスなどでデータベースからHTMLデータを表示させる時に使うと、タグが文字列として出力されてしまいます。
つまり、<p>こんな感じ</p>です。
<p>
タグが表示されていますね。
これは、きちんとXSS対策がされているということが明確に理解できますが、ブログなのでHTMLタグを発動させたいです。
そこで、色々と調べてみました。
(ちなみにGoogle検索で{!! $変数名 !!}
←この記法を検索してもヒットしないので、CSRF保護という名称で検索することを知っておきましょう!)
{!! $変数名 !!}
or <?php html_entity_decode($変数名); ?>
を使うとそのまま表示されるよ!と色々な記事で紹介されていました。
では、そのままこの記法を使って表示させるとHTMLのタグがしっかりと効いてくれるので、やった!と思うかもしれません。
ここが問題で、きちんとXSS対策について理解している前提で、こんな記法もあるんだなと知っておくのは良いのですが、知らずに使ってしまうと危険です。
Laravelの公式ドキュメントにも掲載されていますが、{!! $変数名 !!}
などを使うことはあまりオススメされていません。
HTMLを変数に入れて、echo
などで画面表示することは危険なのでやめておきましょう。
{!! $変数名 !!}
or<?php echo $変数名; ?>
するならサニタイズを忘れずに!
サニタイズとは、危険なコードやデータを変換または除去して無力化する処理のことを言います。
フォームやブログなどの投稿機能を持つものは必ずサニタイズを行うことが求められます。
また、{{ $変数名 }}
を使うことをサニタイジングと呼んでいます。
つまりは、{{ $変数名 }}
を使って全てのタグを無効化してしまうのではなく、自分で許可するタグを設定してから表示させるということです。
$blog = $request->body;
// サニタイズ
$blog = preg_replace('/(?!<\/?(p|a|b|img|u|br|table|tr|th|td|tbody|thead|font|h1|h2|h3|h4|h5)(>|\s[^>]*>))<("[^"]*"|\'[^\']*\'|[^\'">])*>/', '', $blog);
正規表現を使うことによって、必要なタグのみを出力させるようにしています。
ただし、ネストが深い場合は表示がうまくいかない場合があるので、この考え方を元に記述することが必要です。
HTMLやScriptタグを使わない&改行のみであれば以下の記述だけで大丈夫です。
{!! nl2br(e($変数名)) !!}
実際にXSS攻撃テスト(悪用厳禁)をしてみる
実際に、XSS攻撃を自分自身のサイトにテストしてみて、防げているか確認をしておきましょう。
あくまでも、自分自身の制作したサイトのセキュリティ調査の為に行います。
決してXSS攻撃を助長するものではありませんので、ご理解をお願いします。
以下の3つをそれぞれテキスト入力エリアにコピペして、保存や検索、投稿といったボタンでアクションしてみます。
javascript:alert(document.cookie)
<script>alert(document.cookie)</script>
<<script>alert(document.cookie);//<</script>
もし、実際にJavaScriptのアラートが表示されてしまった場合は攻撃が通過しているということになり、XSS攻撃が防げていないことになります。
ちなみに本番環境でレンタルサーバーを使っている場合は、403エラーを返してくれるようになっています。
ローカル環境で試してみてアラート表示がされなければXSS攻撃が防げています。
参考 https://www.agilegroup.co.jp/technote/xss-with-modsec.html (opens in a new tab)