Bokeh üzerinde güvenlik incelemesi yaparken WebSocket origin doğrulamasında Cross-Site WebSocket Hijacking (CSWSH) zafiyeti buldum. Server tarafındaki match_host fonksiyonu hostname’i yanlış karşılaştırıyor; saldırgan kötü bir subdomain kaydederek allowlist’i aşabiliyor.

Zafiyet nerede?

Sorun src/bokeh/server/util.py içinde. match_host, allowlist pattern’leriyle hostname’i Python’un zip() ile karşılaştırıyor. zip() en kısa iterable bittiğinde durduğu için host pattern’den daha uzun olsa bile eşleşme dönüyor.

Zafiyetli kod:

def match_host(host: str, pattern: str) -> bool:
    # ...
    host_parts = host.split('.')
    pattern_parts = pattern.split('.')

    if len(pattern_parts) > len(host_parts):
        return False

    for h, p in zip(host_parts, pattern_parts):
        if h == p or p == '*':
            continue
        else:
            return False
    return True

Pattern’in host’tan uzun olup olmadığına bakılıyor, ama host’un pattern’den uzun olması engellenmiyor. Bu yüzden pattern ile başlayan ek segmentli bir host (örn. example.com.evil.com) yanlışlıkla kabul ediliyor.

Saldırı nasıl işliyor?

Bokeh server allowlist’te örneğin ['dashboard.corp'] varsa:

  1. Saldırgan dashboard.corp.attacker.com gibi bir domain alıyor
  2. O domain’de kötü amaçlı bir site açıyor
  3. Kurbanı bu siteye çekiyor
  4. Site, kurbanın tarayıcısından Bokeh server’a WebSocket bağlantısı açıyor
  5. Origin header (http://dashboard.corp.attacker.com) hatalı mantık yüzünden allowlist ile eşleşiyor
  6. Bağlantı kabul ediliyor; saldırgan kurban adına Bokeh ile etkileşebiliyor

Örnek:

from bokeh.server.util import match_host

allowed_pattern = "example.com"
malicious_origin = "example.com.evil.com"

if match_host(malicious_origin, allowed_pattern):
    print(f"[ZAFİYETLİ] {malicious_origin} {allowed_pattern} ile eşleşti")

Çıktı:

[ZAFİYETLİ] example.com.evil.com example.com ile eşleşti

Eşleşme şöyle oluyor: host['example','com','evil','com'], pattern['example','com']. zip sadece ilk iki çifti karşılaştırıp True dönüyor; geri kalan segment’lere bakılmıyor.

Etki

Cross-Site WebSocket Hijacking mümkün oluyor. Bağlantı kurulduktan sonra saldırgan:

  • Bokeh server’daki hassas verilere erişebiliyor
  • Görselleştirmeleri değiştirebiliyor
  • Kurban adına (WebSocket mesajlarıyla tanımlı) işlem yapabiliyor

Kapsam: Sadece deploy edilmiş Bokeh server instance’ları etkilenir. Static HTML, standalone plot veya Jupyter kullanımı etkilenmez. Authentication hook’ları ayrıca korunmalı; bu zafiyet onları bypass etmez. Internal server’ı tek başına dışarı açmaz.

Düzeltme

Bokeh 3.8.2’de düzeltildi. Host ve pattern’in aynı sayıda segment’e sahip olması zorunlu tutuluyor.

Önerilen mantık:

if len(pattern_parts) != len(host_parts):
    return False

Böylece example.com.evil.com artık example.com ile eşleşmiyor.

Etkilenen / düzeltilmiş sürümler

  • Etkilenen: Bokeh < 3.8.2
  • Düzeltilmiş: Bokeh 3.8.2 ve sonrası

Raporlama

Bokeh güvenlik ekibine bildirdim. GHSA-793v-589g-574v, CVE-2026-21883 atandı, fix 3.8.2’de.

Özet

WebSocket’te origin doğrulama CSWSH’i önlemek için gerekli. Bokeh’teki match_host host’u pattern ile segment sayısına bakmadan kısmen karşılaştırıyordu; saldırgan kötü subdomain ile allowlist’i aşabiliyordu. Hostname/origin kontrolü yaparken pattern ile host’un segment sayısının aynı olduğundan emin ol; kısmi eşleşmeye güvenme.

İlgili içerik

Kaynaklar