リッチエディタと安全なHTML — XSSを防ぐ二重サニタイズ

自由に書ける編集画面と、危険なタグを通さない仕組みを両立させる設計

リッチエディタTiptapXSS対策サニタイズセキュリティ
読了時間: 7分

はじめに

ブログを書く画面は、太字にしたり見出しを付けたり画像を挿入したりできる、いわゆるリッチエディタが便利です。ただ、この「自由に書ける」という性質は、そのままだとセキュリティ上の弱点にもなります。

この記事では、書き心地を損なわずに危険なコードの混入を防いだ方法を紹介します。専門用語が少し出てきますが、考え方はシンプルです。

なぜエディタが危険になり得るのか

XSSという攻撃

リッチエディタで書いた内容は、最終的にHTMLとしてサイトに表示されます。もし記事の中に悪意あるプログラム(スクリプト)が紛れ込んでいたら、それを見た人のブラウザで勝手に実行されてしまいます。

これがXSS(クロスサイト・スクリプティング)と呼ばれる攻撃です。ログイン情報を盗んだり、偽の入力画面を表示したりできてしまいます。

「許可するものを決める」方式にする

危険なタグを見つけて除去する方式(ブラックリスト)だと、新しい攻撃手法が出るたびに漏れが生まれます。そこで使ってよいものだけを列挙し、それ以外はすべて捨てる方式(ホワイトリスト)を採用しました。

記事本文で許可したのは、段落・改行・太字・斜体・下線・打ち消し線・引用・区切り線・見出し・箇条書き・リンク・画像だけです。それ以外のタグは、記事に書かれていても表示時には消えます。

二重にサニタイズする

保存時と表示時、両方で検査する

検査(サニタイズ)は、記事を保存するときと、記事を画面に表示するときの2回行っています。

二重サニタイズの流れ
エディタで執筆

ここでの制限は書き心地のためのもので、安全性の保証ではない

保存時にサニタイズ

許可リストにないタグ・属性をすべて除去してからデータベースへ

表示時にもう一度サニタイズ

データベースの内容を信用せず、表示直前に再度検査

「保存時に検査したのだから表示時は不要では」と思うかもしれません。しかし、保存後にデータベースが直接書き換えられた場合や、過去に別のルールで保存されたデータが残っていた場合を考えると、表示時の検査が最後の砦になります。

エディタ側での制限は、あくまで書き心地のためのものと位置づけています。画面での制限をセキュリティの根拠にしない、という考え方です。

リンクと画像は特に慎重に

リンクのURLには、通常のWebページを指すもの、メール、電話番号のみを許可し、それ以外の形式は除去します。特殊な形式のURLには、スクリプトを埋め込めるものがあるためです。

画像は、暗号化された通信のURLのみを許可しました。データを直接URLに埋め込む形式も、悪用されうるため許可していません。

また、外部サイトへのリンクには自動的に安全属性を付与し、画像には自動的に遅延読み込みの指定を追加します。書き手が意識しなくても、正しい形に整えられる仕組みです。

スタイル指定は文字揃えだけ

文字の配置(左寄せ・中央・右寄せ)は記事の表現として必要なので許可しましたが、それ以外のスタイル指定はすべて禁止しています。

要素の位置を自由に指定できてしまうと、画面上に透明なボタンを重ねるといった手口(クリックジャッキング)が可能になるためです。デザインの自由度は下がりますが、記事本文にそこまでの自由は必要ありません。

便利な機能も安全に作る

お店カードは識別子だけを埋め込む

ブログでお店を紹介するとき、エディタから検索して選ぶと、記事の中にはカード形式の要素が挿入されます。ただし、この要素に埋め込まれるのはGoogleのお店識別子(Place ID)だけです。

表示のたびに最新の店名や評価を取得するので情報が古くならず、記事本文には不正なコードを埋め込む余地もありません。

さらに検査の段階では、「お店カード用の要素で、かつ識別子が正しい形式のもの」以外は、中身ごと削除するルールにしました。似せた要素を作っても通りません。

HTMLを直接編集できる逃げ道も用意する

リッチエディタでは表現しきれない場面のために、HTMLを直接編集するモードも用意しました。ただし、ここで書かれた内容も保存時・表示時のサニタイズを通ります。

自由度を上げつつ、検査は必ず通る。この構造なら、逃げ道を用意しても安全性は変わりません。

まとめ

エディタのセキュリティで押さえたのは、次の3点です。

  1. 許可リスト方式 — 使ってよいタグだけを列挙し、それ以外は無条件で捨てる
  2. 保存時と表示時の二重検査 — データベースの内容も信用しない
  3. 画面での制限を安全の根拠にしない — エディタの制限は書き心地のため、と割り切る