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は入力したテキストを外部に送信しません。読み込みも変換もプレビューも、すべてブラウザの中で完結します。

エディタを開く