XML External Entity (XXE) Attacks: Memahami Ancaman dan Strategi Pencegahan untuk Aplikasi Web Anda
1. Pendahuluan
Di era modern web development, format data seperti JSON (JavaScript Object Notation) telah menjadi raja. API RESTful yang menggunakan JSON mendominasi sebagian besar komunikasi antar layanan. Namun, jangan salah, XML (Extensible Markup Language) masih hidup dan sehat di banyak sistem, terutama di lingkungan enterprise, sistem lama (legacy systems), integrasi pihak ketiga, atau standar industri tertentu (misalnya, SOAP, SAML, RSS, konfigurasi aplikasi, bahkan beberapa format dokumen).
Meski tidak sepopuler dulu, keberadaan XML ini seringkali menjadi “blind spot” bagi developer, terutama yang baru terjun. Akibatnya, kerentanan yang berkaitan dengan XML, seperti XML External Entity (XXE) attacks, sering terabaikan. Padahal, dampak dari serangan XXE bisa sangat fatal, mulai dari pembacaan file sensitif di server, melakukan serangan Server-Side Request Forgery (SSRF), hingga Denial of Service (DoS) yang melumpuhkan aplikasi Anda.
Artikel ini akan membawa Anda memahami apa itu XXE, bagaimana penyerang mengeksploitasinya, dan yang terpenting, strategi praktis untuk melindungi aplikasi web Anda dari ancaman tersembunyi ini. Mari kita jaga aplikasi kita tetap aman, bahkan dari ancaman yang mungkin terlihat “kuno”!
2. Apa itu XML External Entity (XXE)?
Untuk memahami XXE, kita perlu sedikit menyegarkan ingatan tentang dasar-dasar XML, khususnya konsep “Entities” dan “DTD” (Document Type Definition).
XML Entities adalah cara untuk merepresentasikan unit data dalam dokumen XML. Mereka mirip dengan variabel atau makro. Ada dua jenis utama:
- Internal Entities: Didefinisikan dan isinya ada di dalam dokumen XML itu sendiri.
Output:<!DOCTYPE foo [ <!ENTITY greeting "Halo Dunia!"> ]> <root>&greeting;</root><root>Halo Dunia!</root> - External Entities: Ini adalah inti dari masalah XXE. External entities memungkinkan dokumen XML merujuk ke konten dari sumber eksternal. Sumber eksternal ini bisa berupa file lokal di sistem, atau bahkan URL (HTTP, HTTPS, FTP, dll.).
Deklarasi entity ini dilakukan di dalam Document Type Definition (DTD), yang merupakan bagian dari dokumen XML yang mendefinisikan struktur dan elemen yang diizinkan.
Contoh deklarasi external entity:
<!DOCTYPE foo [ <!ENTITY external_file SYSTEM "file:///etc/passwd"> ]>
Di sini, external_file dideklarasikan sebagai external entity yang akan memuat konten dari file /etc/passwd di sistem operasi Linux. Jika XML parser yang memproses dokumen ini dikonfigurasi untuk mengizinkan dan memproses external entities, maka saat entity &external_file; direferensikan di dalam dokumen, konten dari /etc/passwd akan disisipkan.
⚠️ Masalahnya? Banyak XML parser lama atau yang dikonfigurasi secara default mengizinkan pemrosesan external entities. Ini membuka pintu bagi penyerang untuk menyuntikkan referensi ke sumber daya yang tidak seharusnya diakses oleh aplikasi Anda.
3. Bagaimana Serangan XXE Bekerja?
Serangan XXE mengeksploitasi kemampuan XML parser untuk memproses external entities. Penyerang akan mengirimkan input XML yang dibuat khusus untuk memaksa parser membaca data dari lokasi yang tidak diinginkan.
Berikut adalah beberapa skenario serangan XXE yang umum:
Contoh Skenario 1: Membaca File Lokal (Local File Disclosure)
Ini adalah bentuk serangan XXE yang paling umum dan mudah dipahami. Penyerang mencoba membaca file sensitif di sistem server.
🎯 Tujuan Penyerang: Mengakses file konfigurasi, kode sumber, data pengguna, atau informasi sistem lainnya.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>
<name>&xxe;</name>
<email>attacker@example.com</email>
</user>
Jika aplikasi memproses XML ini dan mengembalikan konten dari elemen <name>, penyerang akan menerima isi dari file /etc/passwd. Bayangkan jika file yang dibaca adalah /etc/shadow (berisi hash password), kunci SSH, atau file konfigurasi database!
Contoh Skenario 2: SSRF (Server-Side Request Forgery) melalui XXE
Serangan XXE juga bisa digunakan untuk melakukan SSRF, di mana server dipaksa untuk membuat permintaan ke sumber daya internal atau eksternal yang tidak seharusnya diakses.
🎯 Tujuan Penyerang:
- Memindai port internal di jaringan server.
- Mengakses metadata cloud instance (misalnya AWS EC2 metadata, yang seringkali berisi kredensial sementara).
- Mengakses layanan internal yang tidak terekspos ke internet.
- Mengirim permintaan ke server eksternal untuk melancarkan serangan terhadap pihak ketiga.
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/ec2-role">
]>
<invoice>
<id>&xxe;</id>
<amount>100</amount>
</invoice>
Jika aplikasi memproses XML ini, server akan mencoba mengambil data dari http://169.254.169.254/latest/meta-data/... (alamat IP khusus untuk metadata AWS EC2). Jika berhasil, penyerang bisa mendapatkan kredensial AWS sementara yang sangat berbahaya.
Contoh Skenario 3: Denial of Service (DoS) / Billion Laughs Attack
Serangan ini bertujuan untuk melumpuhkan server dengan membuat XML parser mengkonsumsi sumber daya komputasi (CPU, memori) secara berlebihan.
🎯 Tujuan Penyerang: Membuat aplikasi atau server tidak responsif.
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>
XML ini mendefinisikan entity lol yang berisi “lol”. Kemudian lol2 berisi sepuluh lol, lol3 berisi sepuluh lol2, dan seterusnya. Ketika parser mencoba menyelesaikan &lol9;, ia akan mencoba memperluas string “lol” sebanyak 10^9 kali, yang akan dengan cepat menghabiskan memori dan CPU server, menyebabkan DoS.
4. Mengidentifikasi Kerentanan XXE di Aplikasi Anda
Mendeteksi XXE mungkin tidak semudah XSS atau SQL Injection karena XML tidak selalu terlihat di frontend. Namun, ada beberapa indikator dan pendekatan:
- Input XML: Jika aplikasi Anda menerima input dalam format XML (misalnya, body request dengan
Content-Type: application/xml), ini adalah bendera merah utama. - Library XML Parsing: Periksa kode Anda untuk library yang digunakan untuk memproses XML (misalnya,
DocumentBuilderFactorydi Java,lxmldi Python,XmlDocumentdi C#). - Integrasi Pihak Ketiga: Jika Anda berintegrasi dengan layanan yang menggunakan SOAP, SAML, atau format XML lainnya, pastikan library yang Anda gunakan untuk memproses responsnya aman.
- Pengujian Keamanan:
- SAST (Static Application Security Testing): Tools SAST dapat memindai kode sumber Anda untuk pola-pola yang menunjukkan penggunaan XML parser yang rentan.
- DAST (Dynamic Application Security Testing): Tools DAST atau pengujian penetrasi manual dapat mencoba mengirim payload XXE ke endpoint Anda dan menganalisis responsnya.
- Code Review: Lakukan code review secara berkala, fokus pada bagian kode yang menangani parsing XML.
5. Strategi Pencegahan XXE yang Efektif
Kabar baiknya, pencegahan XXE relatif mudah: 📌 Prinsip Utama adalah menonaktifkan pemrosesan External Entities dan DTD secara default pada XML parser Anda.
Berikut adalah tips praktis untuk berbagai bahasa/framework:
-
Java: Gunakan
DocumentBuilderFactory,SAXParserFactory, atauXMLInputFactory. Pastikan untuk mengatur fitur keamanan yang benar:import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import org.xml.sax.InputSource; import java.io.StringReader; public class SecureXmlParser { public static void main(String[] args) throws Exception { String unsafeXml = "<?xml version=\"1.0\"?>" + "<!DOCTYPE foo [<!ENTITY xxe SYSTEM \"file:///etc/passwd\">]>" + "<root>&xxe;</root>"; DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // ✅ Pencegahan XXE: Menonaktifkan DTD dan External Entities dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // Disallow DOCTYPE declarations dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); // Disable external general entities dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // Disable external parameter entities dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); // Do not load external DTD // Jika Anda menggunakan XInclude, pastikan juga untuk menonaktifkannya dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false); // Penting untuk mencegah Billion Laughs DocumentBuilder db = dbf.newDocumentBuilder(); // Menangani error secara eksplisit, misalnya dengan custom ErrorHandler db.setErrorHandler(new org.xml.sax.ErrorHandler() { @Override public void warning(org.xml.sax.SAXParseException exception) {} @Override public void error(org.xml.sax.SAXParseException exception) throws org.xml.sax.SAXException { throw exception; // Throw error for better security } @Override public void fatalError(org.xml.sax.SAXParseException exception) throws org.xml.sax.SAXException { throw exception; // Throw fatal error } }); try { org.w3c.dom.Document doc = db.parse(new InputSource(new StringReader(unsafeXml))); System.out.println("Parsed XML (securely): " + doc.getDocumentElement().getTextContent()); } catch (Exception e) { System.err.println("Error parsing XML: " + e.getMessage()); } } }⚠️ Penting: Pastikan konfigurasi ini diterapkan di semua tempat Anda memproses XML.
-
PHP (Sebelum PHP 8):
libxml_disable_entity_loader(true);adalah fungsi yang penting untuk menonaktifkan pemuatan external entities. Di PHP 8+, fungsi ini sudah deprecated dan tidak lagi diperlukan karenalibxmlsecara default sudah menonaktifkan pemuatan external entities.<?php // Untuk PHP < 8.0: // libxml_disable_entity_loader(true); // ✅ Menonaktifkan pemuatan external entities $unsafeXml = '<?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <root>&xxe;</root>'; try { $doc = new DOMDocument(); // Mengatur opsi untuk mencegah XXE // LIBXML_NOENT (untuk entity internal) bisa tetap digunakan jika diperlukan, // tapi untuk external entities, pastikan sudah dinonaktifkan secara default atau eksplisit. $doc->loadXML($unsafeXml, LIBXML_NOENT | LIBXML_DTDLOAD | LIBXML_DTDVALID); // ❌ Ini masih rentan jika loader tidak dinonaktifkan // Versi yang lebih aman dengan XMLReader atau simplexml_load_string dengan opsi yang benar // Gunakan XML_PARSE_NOENT untuk menonaktifkan penggantian entitas $safeDoc = simplexml_load_string($unsafeXml, "SimpleXMLElement", LIBXML_NOENT); // ✅ Ini akan mencegah XXE if ($safeDoc === false) { echo "Error parsing XML (securely).\n"; foreach(libxml_get_errors() as $error) { echo $error->message; } } else { echo "Parsed XML (securely): " . $safeDoc->root . "\n"; } } catch (Exception $e) { echo "Error parsing XML: " . $e->getMessage() . "\n"; } ?>✅ Rekomendasi: Di PHP 8+, Anda tidak perlu khawatir tentang
libxml_disable_entity_loader. Pastikan untuk menggunakansimplexml_load_stringatauDOMDocument::loadXMLdengan opsiLIBXML_NOENTuntuk mencegah entitas eksternal. -
Python: Library seperti
lxmlatauxml.etree.ElementTreerentan jika tidak dikonfigurasi dengan benar. Gunakan librarydefusedxmlyang dirancang khusus untuk keamanan atau atur parser secara manual.# Menggunakan defusedxml (direkomendasikan) from defusedxml.lxml import parse as defused_parse from defusedxml.ElementTree import fromstring as defused_fromstring unsafe_xml = """<?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <root>&xxe;</root>""" try: # ✅ defusedxml secara otomatis menonaktifkan XXE root = defused_fromstring(unsafe_xml) print("Parsed XML (securely with defusedxml):", root.text) except Exception as e: print("Error parsing XML (securely with defusedxml):", e) # Menggunakan ElementTree secara manual (membutuhkan konfigurasi) import xml.etree.ElementTree as ET from xml.etree.ElementTree import XMLParser # ✅ Konfigurasi parser untuk menonaktifkan entitas eksternal parser = XMLParser( target=ET.TreeBuilder(), # Menghapus DOCTYPE akan mencegah XXE # Atau gunakan resolve_entities=False jika ada opsi seperti itu di versi Python Anda ) # Di Python 3.x, by default ElementTree tidak memproses external entities. # Namun, lebih baik eksplisit dengan `defusedxml` atau validasi. try: root_manual = ET.fromstring(unsafe_xml) # Ini seharusnya aman di Python 3.x default print("Parsed XML (manual ElementTree, should be safe):", root_manual.text) except Exception as e: print("Error parsing XML (manual ElementTree):", e)✅ Rekomendasi: Gunakan
defusedxmluntuk keamanan yang lebih tinggi. -
Node.js: Node.js tidak memiliki parser XML bawaan. Anda akan menggunakan library pihak ketiga. Pastikan library yang Anda pilih memiliki opsi untuk menonaktifkan DTD dan external entities. Misalnya,
fast-xml-parsermemiliki opsiparseDTDdanresolveExternalEntities.const { XMLParser } = require("fast-xml-parser"); const unsafeXml = `<?xml version="1.0"?> <!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> <root>&xxe;</root>`; const parser = new XMLParser({ // ✅ Menonaktifkan DTD dan external entities parseDTD: false, processEntities: false, // Menangani entitas secara umum htmlEntities: false, // Untuk entitas eksternal, pastikan tidak ada resolver yang aktif // Atau gunakan fitur sanitize jika tersedia }); try { const result = parser.parse(unsafeXml); console.log("Parsed XML (securely):", result); } catch (e) { console.error("Error parsing XML (securely):", e.message);