XML dış varlık (XXE)
- XXE, XML ayrıştırıcısının belge içindeki dış varlık tanımını sunucuda açıp dosya okumasından ya da ağa istek atmasından doğan bir zafiyettir.
- Bu derste varlık türlerini, hata izinden XXE tespitini ve ayrıştırıcı sertleştirmeyi çözümlü örnekle görürsün.
Puan bütün etkinlikleri cevaplayınca hesaba yazılır. Bu dersi tamamlayınca +55 XP alacaksın. Giriş yapmadan ilerlersen puan yalnız bu tarayıcıda durur.
XML belgesi, kendi içinde yeniden kullanılabilir kısayollar tanımlayabilir; bunlara varlık (entity) denir. Belge Türü Tanımı (DTD) bölümünde bir varlık, sabit bir metne ya da dış bir kaynağa bağlanır. Dış varlık (external entity), ayrıştırıcıya bu kısayolu kullanınca şu dosyayı ya da adresi oku der. Standart bir XML ayrıştırıcısı, varsayılan ayarlarla bu talimatı yerine getirir.
Altay Kargo'nun toplu sipariş ucu, iş ortaklarının yüklediği XML dosyasını ayrıştırıp sipariş listesine dönüştürüyor olsun. Dosyanın başına bir DTD bloğu ve dış varlık tanımı eklenirse, ayrıştırıcı o varlığı her gördüğü yerde sunucudaki bir dosyanın içeriğiyle değiştirir. Sipariş listesi alanında beklenmedik biçimde bir yapılandırma dosyasının içeriği belirir.
XML varlıkları, dış varlık ve kök neden
Varlıklar neden var? XML'in amacı, uzun ve tekrarlı belgeleri kolay yönetmekti. Aynı metni elli kez yazmak yerine bir varlık tanımlar, sonra kısayolu kullanırsın; belge küçülür, bakımı kolaylaşır. DTD de belgenin yapısını tanımlamak, zorunlu alanları ve varsayılan değerleri belirtmek için eklendi.
Bu özellikler kötü niyetle değil, kolaylık için kondu. Sorun, dış varlık yeteneğinin dosya ve ağ erişimini de kapsamasıdır. Bugün çoğu uygulama DTD'ye hiç ihtiyaç duymaz, ama ayrıştırıcılar geriye dönük uyum için bu yeteneği varsayılan açık tutar. XXE, kullanılmayan eski bir yeteneğin açık bırakılmasından doğar.
Varlıkların üç türünü ayırmak işini kolaylaştırır. Dahili varlık, belge içinde sabit bir metne bağlanır ve zararsızdır. Dış varlık, SYSTEM anahtar sözcüğüyle sunucudaki bir dosyaya ya da bir ağ adresine bağlanır; XXE'nin kalbi budur. Parametre varlığı ise yalnız DTD bölümünün içinde geçer ve genelde başka tanımları kurmak için kullanılır.
Saldırgan parametre varlıklarını, dizge filtresinden kaçmak ve veriyi parça parça dışarı taşımak için birleştirir. DTD'nin rolü de buradan gelir: dış varlık yalnız bir DTD bloğu içinde tanımlanabilir, bu yüzden bütün savunma DTD işlemenin açık mı kapalı mı olduğuna döner. DTD kapalıysa dış varlık tanımı hiç kurulamaz.
<?xml version="1.0"?> <!DOCTYPE siparis [ <!ENTITY disvarlik SYSTEM "file:///etc/altaykargo/gizli.conf"> ]> <siparis> <musteri>&disvarlik;</musteri> </siparis>
Etkinlik 1 / 7
Bu XML belgesinde dış varlığı tanımlayan satırı bul.
Kök neden yine tanıdıktır: ayrıştırıcı, belge içindeki bir talimatı veriyle aynı kanaldan okur ve veri sanılan kısım aslında bir komuttur. Fark, komutun bu kez dosya okumaya ya da ağa istek atmaya kadar uzanmasıdır; dış varlık tanımı bir adres de taşıyabilir, bu durumda XXE, SSRF'e dönüşür. CWE bu sınıfı CWE-611 olarak kaydeder.
OWASP Top 10:2025, XXE'yi A02 Güvenlik Yanlış Yapılandırması başlığına bağlar, çünkü kök sorun çoğunlukla ayrıştırıcının varsayılan ayarıdır. Aynı zayıflık, ayrıştırıcıyı çağıran her entegrasyonda; e-posta eki, harita dosyası, ofis belgesi gibi XML tabanlı her girişte tekrar eder.
Adım adım işleyişi izle. Ayrıştırıcı belgeyi okur, DTD bloğuna gelir ve varlık tanımlarını belleğe alır. SYSTEM taşıyan bir tanım görünce, kısayolun değerini o dosyanın ya da adresin içeriği olarak çözer.
Belgenin gövdesinde &disvarlik; ifadesine ulaşınca, bu kısayolu çözdüğü içerikle değiştirir. Sonuç, uygulamanın beklediği veri alanına yerleşir ve yanıtla birlikte geri döner. Hiçbir adımda ayrıştırıcı veriyi komuttan ayırmaz; onun için varlık tanımı belgenin meşru bir parçasıdır. Kök neden, güven sınırının belge içinde değil, ayrıştırıcı ayarında olmasıdır.
Etkinlik 2 / 7
Dış varlık tanımındaki SYSTEM anahtar sözcüğü bir dosya yerine bir ağ adresi gösteriyor. Bu durumda XXE neye dönüşür?
Tespit: hata izi ve yanıt sızıntısı
XXE denemesi iki yerde iz bırakır. Ayrıştırıcı hatası fırlatırsa, hata mesajında genelde DOCTYPE ya da ENTITY kelimesi geçer; normal bir sipariş dosyasında bu kelimeler hiç bulunmaz. İkinci iz, yanıt içeriğinde beklenmeyen bir metnin belirmesidir: sipariş listesi yerine bir yapılandırma dosyasının satırları görünür. Sunucunun giden istek kaydı da SYSTEM tanımı bir adres taşıyorsa, SSRF izini gösterir; ayrıştırıcı sunucudan dışarı bir istek atmış demektir.
<!-- Yüklenen sipariş dosyası -->
<?xml version="1.0"?>
<!DOCTYPE siparis [ <!ENTITY d SYSTEM "file:///etc/altaykargo/db.conf"> ]>
<siparis><musteri>&d;</musteri></siparis>
<!-- Dönen sipariş listesi yanıtı: musteri alanında yapılandırma satırları -->
<liste>
<kayit>
<musteri>DB_HOST=10.0.4.20
DB_PAROLA=ornek-deger</musteri>
</kayit>
</liste>Örneği oku: yüklenen belgede d adlı varlık bir yapılandırma dosyasına bağlı. Ayrıştırıcı belgeyi işlerken &d; ifadesini o dosyanın içeriğiyle değiştirir. Sonuçta sipariş listesinin müşteri alanında, beklenen bir ad yerine veritabanı sunucusunun adı ve parolası belirir.
Tespit için tek yapman gereken yanıtın beklenen biçime uyup uymadığına bakmaktır: müşteri adı alanında anahtar-değer satırları görünüyorsa, varlık çözümlemesi gerçekleşmiştir. Buna kör olmayan, bant içi (in-band) XXE denir; veri doğrudan yanıtta döner.
XXE'yi yalnız açık XML yükleme uçlarında arama. XML, pek çok yerde göz önünde değildir: SOAP tabanlı eski servisler, kimlik federasyonunda kullanılan SAML belgeleri, ofis dosyalarının içindeki paketler, hatta bazı yapılandırma biçimleri XML taşır. Bir giriş noktasını kaçırmanın en sık nedeni, verinin XML olduğunu fark etmemektir. Bu yüzden tespitte yalnız dosya uzantısına değil, ayrıştırıcıya giren her gövdeye bakarsın.
Ayrıştırıcı nerede çağrılıyorsa, XXE riski oradadır. Bir güvenlik ekibi bu yüzden önce kod tabanındaki bütün XML okuma noktalarını çıkarır.
Etkinlik 3 / 7
Toplu sipariş yükleme kaydında XXE denemesine ait satırları işaretle.
Şüpheli satırlara tıkla. Birden fazla olabilir.
Varlık genişlemesi: Billion Laughs
XXE'nin dosya okumadan da zarar veren bir kardeşi vardır. Varlık genişlemesinde (CWE-776) saldırgan, bir varlığı başka bir varlığı birçok kez çağıracak biçimde iç içe tanımlar. Her düzey bir öncekini kat kat büyütür; birkaç satırlık belge, ayrıştırıldığında gigabaytlarca metne açılır. Bu teknik, klasik adıyla Billion Laughs, dosya sızdırmaz ama sunucunun belleğini ve işlemcisini tüketerek servisi durdurur.
Savunması iki yönlüdür: ayrıştırıcıda varlık genişlemesini sınırlamak ve yükleme ucunda hem dosya boyutuna hem iç içe varlık sayısına bir tavan koymak. DTD işlemeyi tümden kapatmak, bu saldırıyı da aynı anda keser, çünkü iç içe varlık tanımının kendisi hiç işlenmez.
Varlık genişlemesi, dosya sızdırmadığı için bazen hafife alınır. Oysa etkisi doğrudan hizmet kesintisidir: tek bir küçük belge, ayrıştırıldığında belleği doldurup sunucuyu yanıt veremez hâle getirir. Çok kullanıcılı bir serviste bu, herkesi birden etkileyen bir kesinti demektir. Tespiti de farklıdır: kayıtta hata satırı değil, tek bir yüklemenin ardından fırlayan bellek ve işlemci kullanımı, zaman aşımına düşen istekler ve yeniden başlayan süreçler görürsün.
Python'un yerleşik ayrıştırıcıları dış varlığı varsayılan olarak getirmez. Genişlemeye karşı direnç ise bağlı Expat kütüphanesinin sürümüne kalır; eski sürümler bu saldırıya açıktır. defusedxml bu yüzden hâlâ önerilir. Boyut ve varlık sayısı sınırı, dosya okuma savunması kadar önemlidir.
Derinleş:Parametre varlıkları ve bant dışı sızıntı
Bazı XXE'lerde yanıt hiç görünmez; buna kör XXE denir. Veri bu durumda yanıtla değil, bant dışı (out-of-band) bir kanaldan çıkar ve bu kanalı parametre varlıkları kurar. Parametre varlığı yalnız DTD bölümünde geçerli bir kısayoldur ve adında bir yüzde işareti taşır. DTD içinde bir parametre varlığı, okunan dosyanın içeriğini saldırganın denetimindeki bir adrese ekleyen ikinci bir varlık kurar.
Ayrıştırıcı belgeyi işlerken, dosyanın içeriği bu adrese bir istek olarak gider ve veri sunucudan dışarı sızar. Bu teknik iki şeyi birden gösterir: XXE yanıt dönmese bile tehlikelidir ve parametre varlığı, basit dizge filtrelerinin neden yetmediğini açıklar. Aynı tanım boşluk, satır sonu ya da parametre varlığıyla sayısız biçimde yazılabilir; bu yüzden savunma ayrıştırıcı düzeyinde durur, metin aramasında değil. Dizge filtresi bu varyasyonların hepsini önceden bilemez.
Etkinlik 4 / 7
Birkaç satırlık bir XML dosyası ayrıştırılınca sunucunun belleği doluyor ve servis duruyor. Bu hangi saldırıdır ve en doğrudan savunması nedir?
Savunma: ayrıştırıcı sertleştirme
Kalıcı çözüm, ayrıştırıcıyı dış varlık çözümlemeden kapatmaktır (parser sertleştirme). Çoğu XML kütüphanesi bunu tek ayarla yapar: DTD işlemeyi tamamen kapatmak ya da yalnız dış varlıkları reddetmek. OWASP'ın önerisi nettir: uygulamanın DTD'ye gerçekten ihtiyacı yoksa, ayrıştırıcıyı baştan DTD kabul etmeyecek biçimde yapılandır. Bu ayar, bilinmeyen ya da yeni bir saldırı kalıbını da otomatik kapatır, çünkü sorun kaynağı tamamen devre dışı kalır.
Dile göre ayar adı değişir, ilke değişmez. JavaScript tarafında varlık işlemeyi kapatırsın. Python'da güvenli varsayılanlarla gelen bir kütüphane seçmek, elle ayar yapmaktan daha güvenlidir; defusedxml tam bunu sunar. Java'da DOCTYPE içeren belgeyi tümden reddeden özelliği açarsın.
OWASP, setExpandEntityReferences(false) ayarının bu işi görmediğini özellikle not eder: ayar, varlığın ağaçta nasıl gösterileceğini belirler, getirilip getirilmeyeceğini değil. Ortak hata, bir dilde doğru ayarı yapıp aynı uygulamadaki ikinci bir ayrıştırıcıyı unutmaktır. Büyük bir uygulamada XML birden çok yerde ayrıştırılır; her birini ayrı ayrı sertleştirmen gerekir. Tek bir unutulmuş ayrıştırıcı, bütün savunmayı boşa çıkarır.
// JavaScript (fast-xml-parser): varlık işlemeyi kapat
const ayristirici = new XMLParser({ processEntities: false });
# Python: güvenli varsayılanlarla gelen kütüphaneyi kullan
from defusedxml.ElementTree import fromstring
agac = fromstring(govde)
// Java: DOCTYPE içeren belgeyi tümden reddet
var f = DocumentBuilderFactory.newInstance();
f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
f.setXIncludeAware(false);Sertleştirmeyi yaptıktan sonra sınarsın. Küçük bir DOCTYPE ve zararsız bir dış varlık taşıyan bir test belgesi gönderir, varlığın çözülmediğini doğrularsın; ayrıştırıcı ya hata verir ya da varlığı boş bırakır.
Bu testi kod gözden geçirmesine ve otomatik sınamaya koyarsan, ileride biri ayarı yanlışlıkla gevşettiğinde erken yakalarsın. Aynı testi SVG ve ofis belgesi kabul eden uçlarda da koşarsın, çünkü onların ayrıştırıcısı çoğu zaman başka bir kütüphanedir. Güvenlik ayarı bir kez yapılıp unutulan değil, sürekli doğrulanan bir durumdur.
Etkinlik 5 / 7
Doğru mu, yanlış mı?
XML ayrıştırıcısında DTD işlemeyi tamamen kapatmak, bilinmeyen XXE varyasyonlarını da engeller.
Etkinlik 6 / 7
Bir geliştirici, yüklenen XML dosyasında ENTITY dizgesini arayıp böyle bir satır varsa dosyayı reddediyor. Bu savunmanın asıl zayıflığı nedir?
Yayılma: SVG ve diğer XML biçimleri
Bazı entegrasyonlarda XML'in kendisi gereksizdir. Yeni bir ortaklık kurarken varlık tanımı taşımayan bir biçim tercih etmek, XXE ailesini kökünden kaldırır. Eski ortaklarla XML zorunluysa, yükleme ucunda dosya boyutunu ve iç içe varlık sayısını da sınırlarsın; bu sınır, varlık genişlemesiyle sunucuyu yorma girişimlerini ayrıca keser. Aynı sertleştirme ilkesi, SVG ve ofis belgesi gibi XML tabanlı diğer dosya türleri için de geçerlidir.
Altay Kargo'nun profil fotoğrafı yükleme ucu SVG dosyası kabul ediyorsa, SVG'nin içindeki XML yapısı da aynı dış varlık riskini taşır. Kurumsal uygulamalarda en sık gözden kaçan giriş, görsel sanılan SVG dosyasıdır; ayrıştırıcı sertleştirmesi bu girişte de aynı biçimde kurulmalıdır.
Gerçek bir kurumda bu riski kapatmak sıralı bir iştir. Önce ayrıştırıcıların envanterini çıkarırsın: kod tabanında XML hangi kütüphanelerle, nerede okunuyor? Sonra her birini güvenli ayara çeker, yeni eklenen her ayrıştırıcı için aynı kuralı kod gözden geçirmesine koyarsın. Yükleme uçlarına boyut ve varlık sayısı sınırı eklersin.
Son olarak, mümkün olan yerlerde XML yerine varlık tanımı taşımayan bir veri biçimine geçersin. Bu dört adım, bugünkü ve yarınki XXE denemelerini birlikte kapatır.
Etkinlik 7 / 7
Bir sunucunun beklenmedik biçimde dosya içeriği ya da başka bir sunucudan veri döndürmesinin kök nedeni, ayrıştırıcının ____ varlık tanımını çözümlemesidir.
Okumayı istediğin sırayla sürdürebilirsin. Ders, bütün etkinlikleri cevapladığında tamamlanır.
- Etkinlik 1· XML varlıkları, dış varlık ve kök neden
- Etkinlik 2· XML varlıkları, dış varlık ve kök neden
- Etkinlik 3· Tespit: hata izi ve yanıt sızıntısı
- Etkinlik 4· Varlık genişlemesi: Billion Laughs
- Etkinlik 5· Savunma: ayrıştırıcı sertleştirme
- Etkinlik 6· Savunma: ayrıştırıcı sertleştirme
- Etkinlik 7· Yayılma: SVG ve diğer XML biçimleri