HTML無害化ライブラリsanitize-htmlにXSS脆弱性、即更新を CVE-2026-44990
XSSを防ぐ定番のHTML無害化ライブラリsanitize-htmlに、廃止された古いタグ<xmp>でサニタイズをすり抜ける脆弱性CVE-2026-44990(CVSS 9.3)が見つかりました。週700万DLの広く使われる部品で、対策は2.17.4への更新です。仕組みと点検手順を解説します。

堀川 慎
Backend Engineer / AWS / Django / Go
XSSを防ぐ定番のHTML無害化ライブラリsanitize-htmlに、廃止された古いタグ<xmp>でサニタイズをすり抜ける脆弱性CVE-2026-44990(CVSS 9.3)が見つかりました。週700万DLの広く使われる部品で、対策は2.17.4への更新です。仕組みと点検手順を解説します。
利用者が投稿したHTMLから危険な部分を取り除く「無害化(サニタイズ)」の定番ライブラリsanitize-htmlに、その無害化をすり抜けて攻撃用のスクリプトを通してしまう脆弱性が見つかりました。番号はCVE-2026-44990、深刻度は10点満点中9.3(Critical)です。修正版の2.17.4がすでに公開されており、使っているなら今すぐの更新が推奨されます。
sanitize-htmlは、Node.js(サーバー側でJavaScriptを動かす仕組み)の世界で広く使われているHTMLサニタイザです。npm(JavaScriptのパッケージ配布の仕組み)で週に700万回以上ダウンロードされており、DOMPurifyなどと並ぶ定番の選択肢です。コメント欄やプロフィール、リッチテキスト入力など、利用者が書いたHTMLをそのまま画面に出すと危険な場面で、「安全な部分だけ残す」ためのフィルターとして数えきれないWebアプリに組み込まれています。日本のNode.js/TypeScript製のサービスでも、直接または別のパッケージ経由(推移的依存)で広く使われています。
問題が重いのは、これが「安全のために入れている部品」自身の穴だという点です。XSS(クロスサイト・スクリプティング=他人のサイト上で攻撃者のスクリプトを実行させる攻撃)を防ぐために導入したサニタイザが、特定の書き方をされるとXSSを通してしまう。今回見つかったのは、ほとんど誰も使わなくなった古いHTML要素「<xmp>」を悪用する、シンプルだが効果的なすり抜けでした。
脆弱性の概要
| 項目 | 内容 |
|---|---|
| CVE | CVE-2026-44990(CVSS 9.3 Critical) |
| 対象 | sanitize-html 2.17.4 より前 (2.17.3 以前) |
| 修正版 | 2.17.4 |
| 種別 | XSS(CWE-79) サニタイザのすり抜け |
| 前提 | 既定設定のまま使用 利用者が投稿内容を閲覧 |
| 悪用状況 | 実攻撃・KEV登録なし (PoCは公開済み) |
CVSSベクトルは AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N です。ネットワーク経由(AV:N)・権限不要(PR:N)で攻撃用の内容を投稿でき、被害が出るのはそれを別の利用者が表示したとき(UI:R)です。スコープが変わる(S:C)ため、サニタイザを通したアプリの枠を越えて、閲覧者のブラウザ上で被害が起きます。サーバーを直接乗っ取る種類(RCE)ではなく、閲覧者のブラウザ上でスクリプトが動く保存型XSSが主な影響です。
XSSを防ぐために入れていたサニタイザが、たった一行の古いタグでXSSを通してしまう、というのが今回の核心です。
修正は更新するだけと簡単ですが、見逃すと「対策したつもり」のまま穴が残ります。
安全の網が破れたとき、誰に何が起きるのか
この脆弱性で一番危ういのは、攻撃の入口が「ログインも特別な権限も要らない、ただの投稿」である点です。狙ってくるのは、掲示板やコメント欄・商品レビュー・プロフィール欄・問い合わせフォームに書き込める不特定多数の利用者、なりすまし投稿を量産するスパマー、管理者を狙い撃ちにする標的型の攻撃者です。彼らが持ち去るのは、閲覧した利用者のログイン用Cookie(セッション)、入力中のフォームの中身、画面に表示されている個人情報、そして管理画面を操作する権限です。<xmp>で包んだ一行が保存された瞬間、その投稿を開いた人のブラウザで攻撃者のスクリプトが動き、ログイン中のセッションごと成りすまされてしまいます。
奪われた先の被害は、1人の利用者で止まりません。盗んだセッションで本人になりすませばアカウントは乗っ取られ、もし管理者がその投稿を開いてしまえば、被害は一気にサイト全体へ広がります。攻撃者は全利用者のデータを引き出し、ページの中身を書き換え、改ざんしたページから別のマルウェアを配ったり偽のログイン画面でパスワードを集めたりできます。たった1件のXSSが、サイト全体の改ざんと、全利用者への攻撃の配信に化けてしまうのが、保存型XSSの怖さです。
そして、この穴の責任は、サニタイザを「入れておけば安全」と信じて組み込んだ運用側に返ってきます。漏えいが起きれば、利用者への通知も、個人情報保護委員会などへの報告も、信用の回復も、サービスを提供している側が背負います。CVSS 9.3という数字に表れないのは、「安全対策として入れたはずの部品が裏切る」という、設計の前提そのものが崩れる損失です。だからこそ、いま2.17.4へ更新できているかどうかが、サービスとその利用者の安全を左右します。
sanitize-htmlは何をするライブラリなのか
Webサービスでは、利用者が書いた文章をそのまま画面に表示する場面が数多くあります。コメント、レビュー、プロフィール、お問い合わせ、ブログ本文などです。ここで利用者の入力を無加工で表示すると、悪意ある人が <script> タグなどを混ぜ込み、他の閲覧者のブラウザで勝手にスクリプトを動かせてしまいます。これがXSSです。
sanitize-htmlは、この危険を防ぐための「ふるい」です。利用者が書いたHTMLを受け取り、<b>(太字)や <a>(リンク)のような安全なタグだけを残し、<script> のような危険なタグを取り除いて、安全になったHTMLを返します。開発者はこのライブラリを信頼し、「ここを通したものは安全」という前提でアプリを組み立てています。今回の問題は、その前提が特定の入力で崩れていた、という点にあります。
なぜ古いタグ「<xmp>」で破れたのか
NVDの説明によれば、既定設定(disallowedTagsMode: 'discard')のsanitize-htmlは、許可されていない <xmp> 要素の中身を、生きたHTMLやJavaScriptに変えてしまう、とされています。鍵になるのが、この <xmp> という、いまではほぼ使われない古い要素の性質です。
<xmp> は、中に書かれた内容を「ただの文字(生テキスト)」として扱う、廃止された要素です。sanitize-htmlが内部で使っているHTMLの解析器(htmlparser2)は、仕様どおり <xmp> の中身をタグではなく生テキストとして読み込みます。ところが、sanitize-htmlの「生テキストを扱う処理」が、この内容をエスケープ(無害な文字列への変換)をしないまま出力に書き戻していました。さらに、<xmp> が「中身を生テキストとして扱うべきタグ(nonTextTags)」の既定の一覧から漏れていたことが重なります。結果として、入力時には無害な文字として読まれた内容が、出力時には生きたHTML/JavaScriptに変わるという、サニタイザとして最悪のすり抜けが成立しました。
実証コード(PoC)も単純で、<xmp><script>alert(1)</script></xmp> のような一行がサニタイズをすり抜けます。修正版の2.17.4では、該当の修正コミットで <xmp> を処理対象に正しく加え、この抜け道が塞がれました。GitHubのセキュリティ勧告(GHSA-rpr9-rxv7-x643)でも、この内容が公開されています。なお、この脆弱性を報告したのは @sushi-gif 氏とされています。
影響範囲と対策
影響を受けるのは、sanitize-htmlの2.17.4より前のバージョンを、既定設定のまま使っているアプリです。特別なオプションを付けず、利用者の投稿内容を受け取って画面に表示している場合は対象だと考えてください。やるべきことはシンプルです。
1. sanitize-htmlを2.17.4以降へ更新する。 これが本筋の対応です。npm update sanitize-html などでバージョンを上げ、package-lock.json も含めて2.17.4以上になっているかを確認してください。自分で直接使っていなくても、別のパッケージが内部で古いsanitize-htmlを抱えている(推移的依存)こともあります。npm ls sanitize-html で依存ツリー全体を確認し、取り残しがないかを点検しましょう。
2. すぐ更新できない場合の緩和。 どうしても即時更新が難しいときは、設定で <xmp> を生テキスト扱いのタグ(nonTextTags)に明示的に加えることで、すり抜けの経路を塞げます。ただしこれは応急処置であり、根本対応はあくまでバージョン更新です。
3. 多層防御を見直す。 今回のように「サニタイザ自身が破れる」ことは起こり得ます。出力時のエスケープ、コンテンツセキュリティポリシー(CSP=ブラウザ側でスクリプト実行を制限する仕組み)、CookieのHttpOnly属性など、サニタイザ「だけ」に頼らない守りを重ねておくと、1つの抜け道が即座に被害へ直結するのを防げます。OSSの依存ライブラリの脆弱性を機械的に洗い出す視点については、OSSサプライチェーン・スキャナーの解説もあわせて参照してください。
技術的に見ると ── 「廃止された要素」という落とし穴
HTMLサニタイザのすり抜けは、過去にも繰り返し見つかってきたテーマです。攻撃者は、ブラウザやHTML解析器が持つ「例外的な解釈」を探します。今回の <xmp> のように、ほとんど使われない廃止要素は、仕様上は特殊な振る舞い(中身を生テキストとして扱う)を持つのに、サニタイザ側の対応一覧から抜け落ちやすく、まさに狙い目になります。似た性質を持つ要素には <noscript> や <plaintext> などもあり、サニタイザの「許可・禁止リスト」方式は、こうした隅のケースを取りこぼすと一気に破られます。
教訓は二つあります。一つは、セキュリティ部品ほど最新に保つこと。XSS対策のような「守りの中核」は、抜け道が見つかれば即座に修正が出ます。古いまま放置すると、対策しているつもりが最大の穴になります。もう一つは、守りを一枚に賭けないこと。入力のサニタイズに加え、出力時のエスケープやCSPを重ねておけば、サニタイザが一度破れても被害をその場で食い止められます。今回は実際の攻撃やKEV登録こそ確認されていませんが、PoCがすでに公開され、対象が極めて広く使われるライブラリである以上、更新の先送りは避けたい案件です。
まとめ
XSSを防ぐための定番ライブラリsanitize-htmlに、廃止された古いタグ <xmp> を使ってサニタイズをすり抜け、攻撃者のスクリプトを通してしまう脆弱性(CVE-2026-44990、CVSS 9.3)が見つかりました。週700万回以上ダウンロードされる広く使われた部品で、既定設定のまま利用者の投稿を表示しているアプリが対象です。被害は保存型XSSで、閲覧者のセッション乗っ取りからサイト全体の改ざんまでつながり得ます。
対策は明快で、2.17.4以降への更新です。直接使っていなくても推移的依存で抱えていることがあるため、依存ツリー全体を点検してください。「安全のために入れた部品」が破れる例は今後も起こります。サニタイザだけに頼らず、出力時のエスケープやCSPなど守りを重ねつつ、セキュリティに関わる依存はこまめに最新へ保つことが、結局いちばん確実な対策です。