[技術解説] Jodit EditorにおけるMathML/Style悪用によるMutation XSSの脅威と対策
はじめに
Jodit Editorは、モダンなWebアプリケーションで広く利用されている高機能なWYSIWYGリッチテキストエディタです。ユーザーが入力したHTMLコンテンツを安全に処理するため、強力なサニタイズ機能(`clean-html`プラグイン)を提供しています。しかし今回、このサニタイズ機構を巧妙にすり抜けるMutation XSSの脆弱性(GHSA-rxcw-mc6f-6hr3 / CVE-2026-58263)が発見されました。
この脆弱性を悪用されると、攻撃者が挿入したHTMLコードが完全に除去されず、最終的に表示された際にユーザーの操作なしで悪意のあるスクリプトが実行されてしまう可能性があります。本記事では、この脆弱性の詳細なメカニズムと、日本のフロントエンドエンジニアが取るべき対策について解説します。
脆弱性の概要:Mutation XSSとは?
Mutation XSS(mXSS)とは、ブラウザのHTMLパース処理とJavaScriptによるDOM操作、またはサニタイザーの処理のずれを悪用するXSSの一種です。特に、同一のHTML文字列が異なるコンテキストで複数回パースされる際に、DOMツリーが「変異(Mutation)」し、初回パース時には安全と判断された内容が悪意のあるコードとして解釈されてしまうケースを指します。
今回のJodit Editorの脆弱性もこのカテゴリに属し、Joditの`clean-html`サニタイザーが特定のHTMLを安全と誤認したまま保存・出力し、その出力がWebページ上で再パースされる際にXSSが発動するというメカニズムです。
Jodit Editorのサニタイザーはどのようにバイパスされるのか?
この脆弱性は、ブラウザのHTMLパースルール、特に「MathML Text Integration Point」と「Foster Parenting」という挙動、そしてJoditのサニタイズ処理の順序と範囲を巧妙に悪用します。主なステップは以下の通りです。
攻撃者が用意した特殊なHTML(ペイロード)がJodit Editorにセットされる際、まず`clean-html`プラグインが内部的にサンドボックスDOMにHTMLをパースし、サニタイズ処理を実行します。ペイロードは、以下のような構造を持ちます。
<code><math><mtext><table><mglyph><style><img ... onload=alert(document.domain)></style></mglyph></table></mtext></math></code>
この時、ブラウザのパースルールにより、`<math>`要素内の`<mglyph>`(MathMLのテキスト統合ポイント)がHTMLコンテンツとして解釈されます。しかし、`<style>`タグ(Rawtext要素)がその内部に存在するため、`<style>`内の`<img>`はHTML要素としてはパースされず、単なる「Rawtext(生テキスト)」として扱われます。また、`<table>`はMathMLのコンテキストでは異物とみなされ、「Foster Parenting」ルールによって親要素の外に「浮上」させられます。
Joditの同期的なサニタイザー`safeHTML`は、このサンドボックスDOMツリーを走査しますが、その処理は「HTML要素ノード」を対象としています。そのため、`<style>`内のRawtextとして隠されている`<img>`要素(およびその`onload`ハンドラ)を検出できず、除去されないまま通過させてしまいます。
サニタイザーを通過した後、サンドボックスDOMの`innerHTML`がキャプチャされ、Jodit Editorの実際の値として設定されます。この値をエディタにセットする際、HTMLは再度パースされます。この「再パース」の段階で、状況が変わります。
ブラウザは再度パースを行う際、Rawtextとして扱われていた`<img>`タグを今度はHTML名前空間の正当な「要素」として認識し、`<style>`タグの外に「浮上(Hoist)」させます。これにより、`<img src="..." onload=alert(document.domain)>`という、`onload`ハンドラを保持したままのライブなHTML要素が、サニタイズされたはずの`editor.value`内に含まれてしまうことになります。
Joditには、`on-change`時に動作する非同期のサニタイザーも存在します。このサニタイザーは、ステップ2で浮上した`<img>`要素を検出できますが、その`sanitizeHTMLElement`関数は、`onerror`ハンドラのみを除去するように実装されています。`onload`や`onfocus`といった、`onerror`以外のイベントハンドラは除去されません。
結果として、`editor.value`には、`onload`などの危険なイベントハンドラが除去されずに残り続けることになります。これはJoditの意図するサニタイズ動作を完全に無効化するものです。
具体的なコードの抜粋(Jodit 4.12.27):
<code>// 1. 同期的な値設定パス (clean-html.ts onBeforeSetNativeEditorValue)<br>sandBox.innerHTML = data.value; // :128 parse 1: キャリアが要素を<style>のRawtextとして隠す<br>this.j.e.fire('safeHTML', sandBox); // :129 safeHTML要素ウォークがRawtext要素を見逃す<br>data.value = sandBox.innerHTML; // :130 値がキャプチャされ、再パースで要素が浮上<br>safeHTML(sandBox, { safeJavaScriptLink: true, removeOnError: true }); // :131 SAME parse-1 sandBoxで再実行され、キャプチャされた値には適用されない<br><br>// 2. 非同期のon-changeフィルター<br>sanitizeHTMLElement(nodeElm, { /* ... */ removeEventAttributes: opts.removeEventAttributes }); // sanitize-attributes.ts:30 - full-stripフラグを渡す<br>export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = { /* ... */ }) { // safe-html.ts:139 - removeEventAttributesをデストラクチャリングしない<br> if (removeOnError && elm.hasAttribute('onerror')) attr(elm, 'onerror', null); // onerrorのみ除去; onload / onfocus / ... はそのまま残る<br>}</code>
実際に何が起こるのか? (Proof of Concept)
デフォルト設定のJodit Editorで、攻撃者は以下のようなHTMLを挿入します。
<code>const editor = Jodit.make('#editor');<br>editor.value = '<math><mtext><table><mglyph><style><img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" onload=alert(document.domain)></style></mglyph></table></mtext></math>';</code>
このコードが実行された後、`editor.value`を読み出すと、以下のようなサニタイズ「済み」のHTMLが含まれているはずです。
<code><p><math><mtext><mglyph><style></style></mglyph><br> <img src="data:image/gif;base64,R0lGOD...AAIBRAA7" onload="alert(document.domain)"><br> <table></table></mtext></math></p></code>
このサニタイズ済みとされる`editor.value`を、アプリケーションがDOMにレンダリングすると(例: `document.getElementById('view').innerHTML = editor.value;`)、`<img>`タグの`onload`ハンドラが発火し、`alert(document.domain)`がユーザーのインタラクションなしに実行されてしまいます。これは、一般的なWebアプリケーションにおけるStored XSSの典型的な攻撃シナリオです。
※ブラウザによる挙動の違い:<br>Chromiumでは`element.innerHTML`で即座に発火しますが、Firefoxでは`<iframe srcdoc>`やサーバーサイドレンダリングなどの「ドキュメントパース」コンテキストで発火し、`innerHTML`では`<img>`がMathML名前空間に留まるため発火しません。
影響と対策
この脆弱性は「高 (high)」と評価されており、深刻な影響をもたらします。攻撃者がJodit Editor経由でHTMLコンテンツを挿入できる場合、以下のような被害が発生する可能性があります。
Jodit Editorを利用しているフロントエンドエンジニアは、速やかに以下の対策を検討・実施してください。
まとめ
今回のJodit Editorの脆弱性は、サニタイザーの実装がいかに複雑で、一見安全に見える構成でも巧妙な手法でバイパスされうるかを示しています。特に、ブラウザの特殊なパースルールが悪用されるケースは、一般的なサニタイズロジックでは見落とされがちです。
フロントエンドエンジニアは、たとえリッチテキストエディタのような信頼できるライブラリがサニタイズ機能を提供していても、その内部メカニズムを理解し、常に最新の脅威に注意を払う必要があります。Jodit Editorを利用している場合は速やかに最新バージョンへアップデートし、可能であれば追加のサニタイズレイヤーの導入も検討してください。安全で堅牢なWebアプリケーション開発のため、セキュリティへの意識を高め続けましょう。