MarkdownにHTMLを書けるのはなぜか。サニタイズが防いでいること
Markdownのコラム
MarkdownにはHTMLをそのまま書けます。
仕様として認められている挙動で、変換時に取り除かれることなくHTMLとして出力されます。
<br> を書けば改行になりますし、<table> を書けば表になります。
Markdownの記法で足りないところをHTMLで補える、というのは便利な性質です。
ただし、この性質は変換結果を画面に表示する側から見ると、扱いに注意が要るものになります。
何が起きるのか
Markdownの変換結果をそのまま画面に差し込むと、書かれたHTMLがそのまま動きます。
タグだけでなく、タグに付いた属性も動きます。
たとえば次のような1行です。
<img src="not-found" onerror="alert(1)">画像の読み込みに失敗したときに実行される onerror に、スクリプトが書かれています。
存在しない画像を指定してあるので、表示された時点で必ず実行されます。
自分で書いた文章を自分で表示するだけなら、実害はありません。
問題になるのは、他人が書いたMarkdownを表示する場面です。コメント欄、共有された下書き、外部から取り込んだファイルなど、書いた人と読む人が違う場合です。
こうしたスクリプトの混入をXSS(クロスサイト・スクリプティング)と呼びます。
サニタイズが取り除いているもの
対策は、変換結果を表示する前に通す「サニタイズ」です。
許可したタグと属性だけを残し、それ以外を落とします。
先ほどの例であれば、<img> タグは残り、onerror 属性だけが取り除かれます。
画像は表示され、スクリプトは動きません。
タグごと消すのではなく属性だけを落とすので、見た目を保ったまま危険な部分だけを外せます。
LiteMDでは、プレビューに出す前にDOMPurifyというライブラリを通しています。
標準的なHTMLタグは許可したうえで、スクリプトの実行につながる属性を落とす設定です。
外部リンクの扱いも同じ場所で処理している
サニタイズの工程では、リンクと画像に属性を足す処理も一緒に行っています。
外部サイトへのリンクには target="_blank" と rel="noopener noreferrer" を付けています。
別タブで開くようにするのと、開いた先から元のページを操作されないようにするためです。
画像には loading="lazy" を付けています。画面に入るまで読み込みを遅らせる指定です。
書く側として気にすること
自分で書いて自分で読むだけであれば、この話は気にしなくて構いません。
意識したほうがよいのは、Markdownを受け取って表示する側を作るときです。
Markdownの変換ライブラリは、HTMLを通すのが仕様どおりの動作であって、安全にする役割は持っていません。
サニタイズは別に用意する必要があります。
ブラウザだけで動くMarkdownエディタ
LiteMDは入力したテキストを外部に送信しません。読み込みも変換もプレビューも、すべてブラウザの中で完結します。