انتقل إلى المحتوى

9 NoSQL injection

1. أنواع ثغرات الـ NoSQL Injection

النص بيقول إن في نوعين رئيسيين من الثغرة دي:

  • النوع الأول: Syntax Injection (حقن بناء الجملة)

    • الفكرة: هنا المخترق بيحاول يكسر "الهيكل" أو "القواعد" بتاعة الكود اللي رايح لقاعدة البيانات.

    • إزاي؟ المبرمج بيكون كاتب كود بيستقبل كلمة من المستخدم (مثلاً اسم منتج). لو المستخدم دخل علامات معينة زي " أو ' أو {، الكود الأصلي بينكسر، والمخترق بيقدر يكتب كود أو (Payload) من عنده يتنفذ جوه قاعدة البيانات.

    • الفرق عن الـ SQL العادي: النص بيوضح إن الطريقة دي شبه الـ SQL Injection، لكنها أصعب شوية ومختلفة؛ لأن قواعد الـ NoSQL ملهاش لغة واحدة ثابتة، كل قاعدة بيانات (زي MongoDB, CouchDB, Cassandra) ليها طريقتها وهيكلة البيانات الخاصة بيها.

  • النوع الثاني: Operator Injection (حقن المعاملات)

    • الفكرة: هنا المخترق مش بيكسر الكود، لكن بيستغل "المعاملات" (Operators) اللي بتستخدمها قاعدة البيانات عشان يغير منطق البحث.

    • مثال للتوضيح: في MongoDB في معامل اسمه $ne (يعني Not Equal - لا يساوي). المخترق ممكن يبعت طلب يقول فيه للموقع: "هاتلي بيانات المستخدم اللي اسمه لا يساوي لا شيء"، فيقوم الموقع مرجعله بيانات كل المستخدمين!

2. إزاي نكتشف ثغرة الـ Syntax Injection؟

النص بيشرح استراتيجية لاكتشاف الثغرة دي، وهي استخدام حاجة اسمها Fuzzing.

  • يعني إيه Fuzz Strings؟ هي عبارة عن نصوص عشوائية مليانة رموز خاصة (زي الأقواس، علامات التنصيص، علامات الدولار، وغيرها).

  • الهدف: إنت بتبعت الرموز دي للموقع (في خانة البحث مثلاً)، وتشوف الموقع هيتصرف إزاي. لو الموقع مش بيعمل "تنظيف" (Sanitization) للمدخلات دي، الرموز دي هتدخل تلخبط قاعدة البيانات وتعمل Error (خطأ برمجي) أو تخلي الصفحة تحمل بشكل غريب. لو ده حصل، بنعرف إن الموقع ده "مصاب" وممكن اختراقه.

3. المثال العملي على MongoDB (زي ما النص ذكره)

تخيل إنك في موقع تسوق، وضغطت على قسم "المشروبات الغازية" (Fizzy drinks).

  • الوضع الطبيعي: المتصفح بيبعت الرابط ده: https://insecure-website.com/product/lookup?category=fizzy الموقع بياخد كلمة fizzy ويبعتها لقاعدة البيانات بالشكل ده: this.category == 'fizzy' (يعني هاتلي المنتجات اللي قسمها يساوي fizzy).

  • وضع الاختبار (الهجوم): النص بيقولك بدل ما تبعت كلمة fizzy العادية، هنبعت الـ Fuzz String المعقد ده: '"{;$Foo}$Foo \xYZ *ليه السلسلة دي بالذات؟* لأنها بتحتوي على كل الرموز اللي ممكن تقفل الـ String بتاع المبرمج وتفتح كود جديد (', ", {, }`).

    الرابط هيكون شكله كده (بعد تحويل الرموز لصيغة يقبلها الرابط - URL Encoding): https://insecure-website.com/product/lookup?category='%22%60%7b%0d%0a%3b%24Foo%7d%0d%0a%24Foo%20%5cxYZ%00

  • النتيجة المنتظرة: الكود جوه قاعدة البيانات هيبقى شكله ملخبط جداً ومكسور. لو الموقع رجعلك استجابة مختلفة عن المعتاد (مثلاً صفحة بيضاء، أو رسالة خطأ زي Internal Server Error 500)، ده معناه إن كلامك (الرموز اللي بعتها) اتنفذت كأنها كود مش مجرد نص عادي.. مبروك، أنت كده اكتشفت ثغرة!


[^1]

[^1]: الهدف الأساسي من الـ "Fuzz String" ده هو إنه يخبط في كل الاحتمالات الممكنة اللي المبرمج ممكن يكون كاتب بيها الكود بتاعه، عشان يكسره ويجبر قاعدة البيانات تطلع رسالة خطأ (Error).

تعالى نحلل النص ده:

Plaintext

```
'"`{
;$Foo}
$Foo \xYZ
```

واللي لما بيتحول لصيغة الرابط (URL Encoded) بيبقى شكله كده: `'%22%60%7b%0d%0a%3b%24Foo%7d%0d%0a%24Foo%20%5cxYZ%00`

### تفريغ الكود (رمز برمز):

- **`'` و `"` و `` ` `` (علامات التنصيص بأنواعها):** المبرمج ممكن يكون كاتب الكود بتاعه جوه علامات تنصيص مفردة (Single Quotes) زي كده: `'fizzy'`، أو مزدوجة (Double Quotes) `"fizzy"`، أو حتى Backticks في الجافاسكريبت `` `fizzy` ``. إحنا بنبعت التلاتة عشان أيًا كان المبرمج استخدم إيه، نقدر نقفل النص بتاعه ونبدأ نكتب الكود بتاعنا. _(في الرابط بتترجم لـ: `%22` و `%60` بالإضافة للـ `'` العادية)._

- **`{` و `}` (أقواس المجموعات):** قاعدة بيانات MongoDB بتعتمد على هيكل بيانات اسمه JSON، واللي كله مبني على الأقواس دي `{ }`. إحنا بنبعت الأقواس دي عشان نحاول نلخبط هيكل الـ JSON أو نقفل Block كود مفتوح. _(في الرابط بتترجم لـ: `%7b` و `%7d`)._

- **النزول لسطر جديد (New Line):** في النص الأصلي هتلاقي الكود مكتوب على 3 سطور. النزول لسطر جديد ده مقصود عشان لو المبرمج عامل "تعليق" (Comment) لبعض الأكواد، النزول لسطر جديد بيكسر التعليق ده ويجبر السيرفر يقرا الكود. _(في الرابط بتترجم لـ: `%0d%0a` واللي هي اختصار لـ Carriage Return & Line Feed)._

- **`;` (الفاصلة المنقوطة - Semicolon):** في لغات برمجة كتير (زي الجافاسكريبت اللي بتعتمد عليها MongoDB)، الفاصلة المنقوطة معناها "السطر ده خلص، نفذ السطر اللي بعده". إحنا بنستخدمها عشان ننهي أمر المبرمج، ونقول لقاعدة البيانات "دلوقتي نفذي الأمر بتاعي أنا". _(في الرابط بتترجم لـ: `%3b`)._

- **`$Foo` (علامة الدولار مع كلمة):** في MongoDB، كل المعاملات (Operators) القوية اللي بتعمل بحث أو استعلام بتبدأ بعلامة الدولار (زي `$where` و `$ne` و `$gt`). المخترق بيبعت `$Foo` (كمتغير عشوائي أو غير معروف) عشان يشوف هل قاعدة البيانات هتتعامل معاه على إنه أمر غير صالح وتطلع Error، ولا هتعديه عادي؟ _(في الرابط بتترجم لـ: `%24Foo`)._

- **`\xYZ` (علامة الهروب الخاطئة - Invalid Escape Sequence):** عادةً في البرمجة، الرمز `\x` بييجي وراه أرقام وحروف معينة (Hexadecimal). هنا المخترق باعت `YZ` (ودي حروف غير صالحة في النظام السداسي عشر). الهدف هنا هو إجبار محرك الجافاسكريبت جوه MongoDB إنه يطلّع Syntax Error صريح لأنه مش هيعرف يترجم الكلمة دي. _(في الرابط بتترجم لـ: `%5cxYZ`)._

- **`%00` (الـ Null Byte):** دي مش مكتوبة في النص العادي لكن محطوطة في آخر الرابط. الرمز ده سحري شوية، ومعناه للمتصفح والسيرفر: "النص خلص هنا، تجاهل أي حاجة مكتوبة بعد النقطة دي". ده بيساعد المخترق يتجاهل أي كود المبرمج كان ضايفه بعد كلمة البحث.


**الخلاصة:** إنت مش بتبعت كلمة يبحث عنها، إنت بتبعت "قنبلة" من الرموز البرمجية. لو الموقع محمي كويس (بيعمل Sanitization)، هيعتبر كل الرموز دي مجرد "نص عادي" وهيبحث عن منتج اسمه `'"`{;$Foo}...` ومش هيلاقي حاجة. لكن لو الموقع مش محمي، الرموز دي هتتنفذ كأنها **أوامر برمجية**، وتكسر الاستعلام الأصلي، وتكشف لك إن الموقع مصاب بثغرة Syntax Injection!

1. تحديد الحروف المؤثرة (Testing Individual Characters)

في الخطوة دي، إنت بتحاول تتأكد بنسبة 100% إن الموقع فعلاً مصاب ومش مجرد علق بالصدفة.

  • كسر الكود: بدل ما تبعت كلمة طويلة ومعقدة، بتبعت حرف واحد بس وهو علامة التنصيص المفردة '. الكود في قاعدة البيانات هيبقى شكله كده: this.category == '''. التلات علامات ورا بعض دول هيعملوا خطأ برمجي (Syntax Error) والموقع هيتصرف بشكل غريب.

  • تأكيد الثغرة (الهروب): عشان تتأكد إن الخطأ ده حصل بسبب الثغرة، بتبعت علامة التنصيص ومعاها شرطة مايلة كده \'. الشرطة المايلة دي في البرمجة اسمها (Escape Character)، وبتقول لقاعدة البيانات: "اعتبري علامة التنصيص اللي جاية دي مجرد حرف عادي مش أمر برمجي". لو الموقع رجع اشتغل طبيعي ومفيش خطأ ظهر، يبقى إنت كده اتأكدت إن الموقع مصاب وثغرتك شغالة.

2. اختبار السلوك الشرطي (Confirming Conditional Behavior)

دلوقتي إنت اتأكدت إنك بتقدر تكتب كود، عايزين بقى نختبر هل نقدر نتحكم في "منطق" قاعدة البيانات ولا لأ (يعني نتحكم في الـ True و الـ False).

  • تجربة الشرط الخاطئ (False): بتبعت الكود ده ' && 0 && 'x. علامة && معناها (و - AND)، ورقم 0 في البرمجة معناه (خطأ - False). هنا إنت بتقول للموقع: "هاتلي قسم المشروبات الغازية و كمان شرط مستحيل يحصل". النتيجة؟ الموقع مش هيرجعلك أي منتجات خالص.

  • تجربة الشرط الصحيح (True): بتبعت الكود ده ' && 1 && 'x. رقم 1 معناه (صحيح - True). هنا بتقول للموقع: "هاتلي قسم المشروبات الغازية و كمان شرط صحيح". النتيجة؟ الموقع هيرجعلك المنتجات بشكل طبيعي جداً.

  • الاستنتاج: طالما الموقع استجاب للـ 0 مرة وللـ 1 مرة تانية، يبقى إنت بقيت بتتحكم في عقل قاعدة البيانات من بره! (حرف الـ x' في الكود هدفه بس يقفل أي علامات تنصيص مفتوحة في الكود الأصلي عشان مايحصلش Error).

3. تخطي الشروط الأساسية (Overriding Existing Conditions)

دي بقى "الضربة القاضية". إنت دلوقتي عرفت إنك بتتحكم في الشروط، فهتستخدم ده عشان تخلي قاعدة البيانات تعرضلك كل حاجة عندها، حتى المنتجات المخفية أو اللي لسه مانزلتش السوق.

  • الهجوم: بتبعت الكود ده '||'1'=='1. علامة || معناها (أو - OR).

  • الكود النهائي في قاعدة البيانات هيبقى كده: this.category == 'fizzy'||'1'=='1' this.category == 'fizzy%27%7c%7c%27%31%27%3d%3d%27%31

  • الترجمة للمنطق: إنت بتقول لقاعدة البيانات: "هاتلي المنتجات اللي قسمها مشروبات غازية، أو المنتجات اللي بينطبق عليها إن رقم 1 يساوي رقم 1".

  • النتيجة: لأن رقم 1 دايماً بيساوي 1 (ده شرط صحيح دايماً True)، قاعدة البيانات هتتجاهل شرط القسم خالص، وهترجعلك كل المنتجات الموجودة في الموقع دفعة واحدة!


1. خدعة الـ Null Byte (تجاهل باقي الشروط)

تخيل إن الموقع مش بس بيبحث عن القسم، لأ ده كمان بيضيف شرط من عنده عشان يتأكد إن المنتج ده "متاح للبيع" (Released) ومش لسه في المخزن.

  • الكود الأصلي في قاعدة البيانات:

    JavaScript

    this.category == 'fizzy' && this.released == 1

    الموقع هنا بيقول: "هاتلي المنتجات اللي قسمها مشروبات غازية، وكمان يكون شرط (released) يساوي 1 (يعني متاح للبيع)". لو منتج مخفي، قيمته هتكون 0 فمش هيظهر للناس.

  • كود الهجوم: أنت كمخترق بتبعت الرابط ده: https://insecure-website.com/product/lookup?category=fizzy'%00

    الرمز السحري هنا هو %00 (اللي بيترجم برمجياً لـ \u0000 أو Null Byte). الرمز ده في لغات برمجة كتير معناه "النص خلص هنا، أي حاجة مكتوبة بعدي ملهاش لازمة وما تقراهاش".

  • النتيجة بعد الهجوم:

    JavaScript

    this.category == 'fizzy'\u0000' && this.released == 1

    قاعدة البيانات هتقرأ لحد كلمة 'fizzy' وتلاقي الـ Null Byte، فتقوم متجاهلة تماماً الجزء بتاع && this.released == 1. النتيجة؟ الموقع هيعرضلك كل المشروبات الغازية، حتى المنتجات المخفية اللي لسه مانزلتش السوق!

2. النوع الثاني: Operator Injection (حقن المعاملات)

هنا إحنا مش بنحاول "نكسر" الكود بعلامات تنصيص، إحنا بنستخدم أدوات لغة MongoDB نفسها ضدها. في MongoDB، أي أمر بيبدأ بعلامة دولار $ بيتسمى "معامل" (Operator).

أهم المعاملات اللي بنستخدمها:

  • $where: بيسمحلك تكتب كود جافاسكريبت كامل يتنفذ جوه قاعدة البيانات.

  • $ne: اختصار لـ Not Equal (لا يساوي).

  • $in: اختصار لـ In Array (موجود في القائمة).

  • $regex: للبحث المتقدم بالنصوص (Regular Expressions).

3. إزاي بنحقن المعاملات دي؟ (طرق الإرسال)

النص بيشرحلك إنك عشان تبعت المعاملات دي للسيرفر، بتغير شكل البيانات اللي بتبعتها:

  • في نظام الـ JSON (لو الموقع بيستقبل بيانات زي الـ API): بدل ما تبعت اسم المستخدم ككلمة عادية كده: {"username":"wiener"} بتحوله لـ "كائن متداخل" (Nested Object) وتدخل المعامل بتاعك: {"username":{"$ne":"invalid"}}

  • في نظام الروابط (URL Parameters): بدل ما تبعت ?username=wiener بتخليها كده: ?username[$ne]=invalid

💡 نصيحة احترافية: النص بيقولك لو جربت تحقن في الرابط والموقع رفض، تقدر تفتح أداة زي Burp Suite وتحول الطلب من GET (عن طريق الرابط) إلى POST (بيانات مخفية)، وتغير نوع البيانات (Content-Type) لـ application/json. ده بيخلي جدار الحماية (WAF) يتلخبط ويسمحلك تعدي.

4. التطبيق العملي: تخطي صفحة تسجيل الدخول (Login Bypass)

ده أمتع جزء! تخيل قدامك صفحة Login بتطلب يوزر وباسورد.

  • الوضع الطبيعي للطلب (Normal Request):

    JSON

    {"username":"wiener","password":"peter"}

    قاعدة البيانات بتدور على شخص اسمه wiener والباسورد بتاعه peter. لو لقته بتدخلك.

  • هجوم التخطي الأعمى (Blind Bypass): أنت مش عارف أي يوزر ولا أي باسورد، فبتبعت الكود ده:

    JSON

    {"username":{"$ne":"invalid"},"password":{"$ne":"invalid"}}

    الشرح: أنت هنا بتقول لقاعدة البيانات: "سجل لي دخول بأي مستخدم اسمه لا يساوي كلمة 'invalid'، والباسورد بتاعه لا يساوي كلمة 'invalid'". النتيجة: لأن كل المستخدمين في الموقع أسماءهم مش 'invalid'، الشرط هيتحقق، وقاعدة البيانات هتسجل دخولك بأول حساب يقابلها في السجلات (واللي غالباً بيكون حساب الـ Admin لأنه أول حساب بيتكريت!).

  • هجوم التخطي المستهدف (Targeted Bypass): لو أنت عاوز تخترق حساب محدد (زي الأدمن)، ومش عاوز الموقع يدخلك على أي حساب عشوائي، بتستخدم معامل $in بالشكل ده:

    JSON

    {"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}

    الشرح: 1. معامل $in: بتقول للموقع دورلي على مستخدم اسمه موجود جوه القائمة دي (admin أو administrator أو superadmin). 2. معامل $ne: بتقول للموقع "والباسورد بتاعه لا يساوي (فراغ)". النتيجة: الموقع هيلاقي حساب الأدمن، وهيتأكد إن الباسورد بتاعه مش فاضي (وهو فعلاً مش فاضي)، فهيدخلك على حساب الأدمن مباشرة بدون ما تكتب حرف واحد من الباسورد الحقيقي!


Exploiting NoSQL operator injection to bypass authentication LAB:

1. تسجيل الدخول بحساب wiener:peter (مرحلة الـ Reconnaissance)

JSON

{"username": "wiener", "password": "peter"}

الخطوة دي هدفها إنك تشوف "الشكل الطبيعي" للـ Request والـ Response لما السيرفر بيقبل بيانات صحيحة (زي الـ Redirect والـ 302 Found). دي بتكون نقطة المرجع (Baseline) بتاعتك.

2. اختبار الـ Operators على الـ Username (مرحلة الـ Testing)

JSON

{"username": {"$ne":""}, "password": "peter"}

هنا إنت بتسأل السيرفر: "هل بتفهم الـ Operators؟". إنت بتقوله: "سجل دخولي بحساب الباسورد بتاعه peter، واسم المستخدم بتاعه لا يساوي (فراغ)". بما إن مفيش غير حساب wiener هو اللي الباسورد بتاعه peter، السيرفر هيرجع حساب واحد بس، وهيسجل دخولك بنجاح. ده بيأكدلك بنسبة 100% إن الـ Operator Injection شغال في خانة اليوزر.

3. اختبار قوة الـ Regex

JSON

{"username": {"$regex":"wien.*"}, "password": "peter"}

هنا بتختبر الـ Regular Expressions. العلامة .* في الـ Regex معناها "أي عدد من الحروف". فإنت بتقوله: "هاتلي المستخدم اللي اسمه بيبدأ بكلمة wien وبعدها أي حاجة". السيرفر هيفهمها ويجيب wiener، وتدخل عادي. إنت كده اتأكدت إن الـ Regex كمان شغال ومفيش حماية ضده.

4. تأكيد الثغرة في الباسورد (وهنا بيحصل الـ Error اللي شفناه)

JSON

{"username": {"$ne":""}, "password": {"$ne":""}}

دي نفس الخطوة اللي إحنا عملناها وطلعت لنا Query returned unexpected number of records. اللاب قاصد يخليك تعمل كده عشان تفهم إنك لما بتلغي شرط اليوزر وشرط الباسورد مع بعض، قاعدة البيانات بترجع كل المستخدمين. وبما إن الكود بتاع الموقع متبرمج إنه يستقبل "حساب واحد فقط"، بيضرب Error 500. الخطوة دي بتأكدلك إن الثغرة موجودة في خانة الباسورد كمان، بس محتاجة تتظبط.

5. الضربة القاضية (مرحلة الـ Exploitation)

JSON

{"username": {"$regex":"admin.*"}, "password": {"$ne":""}}

هنا بقى الـ Bypass الحقيقي والحل السحري للمشكلة بتاعتنا:

  • "username": {"$regex":"admin.*"}: إنت هنا بتقول للسيرفر هاتلي المستخدم اللي اسمه بيبدأ بكلمة admin وبعده أي حروف (عشان لو اسمه administrator أو admin أو admin123، الكود يصطاده من غير ما تعرف اسمه بالكامل). ده بيضمن إن قاعدة البيانات هترجع حساب واحد بس (حساب الأدمن)، فالسيرفر مش هيطلع Error.

  • "password": {"$ne":""}: وفي نفس الوقت، بتقول للسيرفر "تخطى فحص الباسورد طالما هو مش فاضي".

النتيجة: السيرفر بيلاقي حساب الأدمن، بيتخطى الباسورد، وبيرجعلك سجل واحد بس، فبيديك الـ Session أو الـ Redirect، وبتدخل كأدمن واللاب بيتحل!

الخلاصة: إحنا كنا صح جداً لما استخدمنا $ne وشفنا الـ Error، وكنا صح لما فكرنا في الـ $regex. التريكة كلها كانت في دمجهم مع بعض واستخدام admin.* عشان نجبر السيرفر يرجّع يوزر واحد بس ونتفادى رسالة الـ unexpected number of records اللي كانت بتوقفنا.


النص بيشرح إزاي نستغل دالة خطيرة جداً في MongoDB اسمها $where. الدالة دي بتسمح للمبرمج إنه يكتب أوامر بـ JavaScript وتتنفذ جوه قاعدة البيانات. وطالما بنقدر نكتب JavaScript، يبقى نقدر نبرمج قاعدة البيانات تطلع لنا اللي إحنا عايزينه.

تعالى نفصص الأكواد ونفهم اللعبة ماشية إزاي:

1. استخراج الباسورد حرف بحرف (Character Extraction)

تخيل إن الموقع بيبحث عن المستخدمين بالكود ده في الخلفية: {"$where":"this.username == ' [مدخلات المستخدم] '"}

أنت كمخترق هتبعت الـ Payload ده: admin' && this.password[0] == 'a' || 'a'=='b

إيه اللي هيحصل جوه قاعدة البيانات؟ الكود هيبقى شكله كده: {"$where":"this.username == 'admin' && this.password[0] == 'a' || 'a'=='b'"}

شرح الكود حتة حتة:

  • 'admin: إنت قفلت اسم المستخدم الأول عشان تبدأ تكتب الكود بتاعك.

  • &&: (و) بتضيف شرط جديد.

  • this.password[0] == 'a': ده السؤال الخبيث! إنت بتسأل قاعدة البيانات: "هل أول حرف (Index 0) في باسورد الأدمن هو حرف الـ a؟".

  • || 'a'=='b': (أو) شرط خاطئ للتمويه. أهمية الجزء ده إنه بيمسك علامة التنصيص الأخيرة ' اللي السيرفر بيحطها في نهاية الكود، فبيحولها لـ 'a'=='b' عشان مايحصلش Syntax Error والكود يضرب.

النتيجة المنطقية: لو السيرفر رجعلك بيانات الأدمن عادي، يبقى أول حرف في الباسورد هو فعلاً a. لو السيرفر مارجعش حاجة (أو رجع User not found)، يبقى الحرف غلط. في الحالة دي، هتروح لبرنامج زي Burp Suite، وتستخدم أداة الـ Intruder عشان تجرب الحروف كلها أوتوماتيك (a, b, c, d...) لحد ما تكتشف الحرف الأول، وبعدين تغير [0] لـ [1] عشان تجيب الحرف التاني، وهكذا لحد ما تجمع الباسورد كله!

2. البحث عن أنماط معينة في الباسورد (استخدام الـ Regex)

عشان توفر على نفسك وقت التخمين الطويل، ممكن تسأل السيرفر أسئلة عامة الأول عن شكل الباسورد باستخدام دالة match() في الجافاسكريبت.

أنت هتبعت الـ Payload ده: admin' && this.password.match(/\d/) || 'a'=='b ذي

GET /user/lookup?user=administrator'%20%26%26%20this.password[0]%20%3d%3d%20'a'%20||%20'a'%3d%3d'b HTTP/2
Host: 0ad700c9035911d3802dcb9400ff002b.web-security-academy.net
Cookie: session=VJ5CmBEdtlxKKnJffPxOyk8NIHTcBNFb

ملاحظة : لازم تعمل URL Encode لل payload

شرح الكود:

  • الجزء الأول والأخير نفس الفكرة اللي فاتت بالظبط.

  • this.password.match(/\d/): هنا إنت بتستخدم تعبير نمطي (Regular Expression). الرمز \d معناه أرقام (Digits). إنت بتسأل قاعدة البيانات: "هل باسورد الأدمن بيحتوي على أي أرقام جواه؟".

النتيجة المنطقية: لو السيرفر استجاب ورجع بيانات الأدمن، يبقى الباسورد فيه أرقام، وساعتها لما تيجي تخمن بالـ Intruder هتركز على الأرقام. لو مارجعش حاجة، يبقى الباسورد كله حروف، فهتوفر وقت التخمين في الأرقام تماماً.

الخلاصة: الطريقة دي بتعتمد على إنك بتسأل قاعدة البيانات أسئلة إجابتها (أيوة أو لأ - True or False). ومن خلال مراقبة استجابة السيرفر، بتقدر تستنتج البيانات المخفية وتجمعها قطعة قطعة. ده بالظبط نفس كونسبت الـ Blind SQL Injection بس مطبق على الـ NoSQL والجافاسكريبت.


علشان تعرف ال password length :

الفكرة ببساطة:

بدل ما نسأل قاعدة البيانات "هل أول حرف هو كذا؟"، هنسألها المرة دي: "هل طول الباسورد بيساوي رقم كذا؟"

الكود اللي هنحقنه هيكون شكله كده: administrator' && this.password.length == 8 || 'a'=='b

شرح الكود: أنت بتقول للموقع: "هاتلي بيانات الأدمن، بشرط إن طول الباسورد بتاعه يكون بيساوي 8 حروف".

إزاي تنفذها عملي في Burp Suite:

1. تجهيز الطلب (Request): هتاخد الكود وتعمله URL Encoding، وتحطه في الـ GET request بتاعك. ده شكل الطلب اللي هتبعته للـ Repeater عشان تجربه:

HTTP

GET /user/lookup?user=administrator'%20%26%26%20this.password.length%20%3d%3d%208%20||%20'a'%3d%3d'b HTTP/2
Host: 0ad700c9035911d3802dcb9400ff002b.web-security-academy.net

2. الأتمتة باستخدام الـ Intruder: بدل ما تجرب الأرقام بإيدك (1، 2، 3...)، هتبعت الطلب ده لـ Intruder.

  • هتعمل تحديد (Highlight) على رقم 8 وتخليه هو الـ Payload Position بتاعك.

  • في قسم الـ Payloads، هتختار النوع Numbers.

  • هتحدد النطاق (مثلاً من 1 إلى 20)، وتخليه يعد خطوة بخطوة (Step: 1).

  • هتبدأ الهجوم (Start Attack).

3. قراءة النتيجة: الـ Intruder هيبعت 20 طلب. كل الطلبات هترجع استجابة مختلفة (غالباً حجمها هيكون صغير أو هترجع رسالة خطأ/مستخدم غير موجود) ما عدا طلب واحد بس. الطلب اللي هيصادف الرقم الصحيح لطول الباسورد، هو الوحيد اللي هيرجعلك بيانات الأدمن و200 OK (حجم الاستجابة Length هيكون أكبر ومختلف). الرقم ده هو طول الباسورد بالظبط!

بعد ما تعرف الطول (مثلاً طلع 8 حروف)، هترجع بقى للكود بتاع استخراج الحروف (this.password[0] == 'a')، وتبقى عارف إنك هتعمل الهجوم من الـ Index رقم 0 لحد الـ Index رقم 7.


Identifying field names:

عشان تفهم الجزء ده، لازم نعرف الأول إيه الفرق الجوهري بين قواعد البيانات القديمة (SQL) والجديدة زي MongoDB (NoSQL).

المشكلة: مفيش هيكل ثابت (No Fixed Schema)

في قواعد البيانات العادية، الجداول بتبقى ثابتة؛ لو في خانة اسمها password، هتبقى موجودة عند كل الناس. لكن في MongoDB، البيانات "شبه مهيكلة" (Semi-structured). ده معناه إن ممكن يكون خانة الرقم السري متسمية password، وممكن المبرمج يكون مسميها pwd أو pass أو secretHash.

عشان كده، قبل ما نضيع وقت في استخراج حروف الباسورد زي ما عملنا في الخطوة اللي فاتت، لازم نأكد الأول إن الخانة اللي بنستهدفها اسمها فعلاً password.

الحل: استراتيجية المقارنة (Baseline Testing)

النص بيشرح تكنيك ذكي جداً بيعتمد على إننا نسأل السيرفر 3 أسئلة: سؤال إجابته أكيد صح، وسؤال إجابته أكيد غلط، وسؤال عن الخانة اللي بندور عليها، وبعدين نقارن الاستجابات.

1. اختبار خانة إحنا متأكدين إنها موجودة (True Baseline):

JavaScript

admin' && this.username!='

أنت هنا بتقوله: "هاتلي بيانات الأدمن، بشرط إن خانة الـ username متكونش فاضية". بما إننا متأكدين إن خانة اليوزر نيم موجودة، السيرفر هيستجيب بنجاح ويرجع بيانات الأدمن. (كده عرفنا شكل الاستجابة الناجحة).

2. اختبار خانة إحنا متأكدين إنها مش موجودة (False Baseline):

JavaScript

admin' && this.foo!='

أنت هنا بتسأل عن خانة اسمها foo (اسم وهمي). لأن الخانة دي مش موجودة أصلاً في قاعدة البيانات (قيمتها بتكون Undefined)، الشرط مش هيتحقق، والسيرفر هيرجع استجابة مختلفة (سواء Error، أو يقولك مفيش مستخدم بالاسم ده، أو يرجع صفحة فاضية). (كده عرفنا شكل الاستجابة الفاشلة).

3. اختبار الخانة المستهدفة (The Target):

JavaScript

admin' && this.password!='

دلوقتي إحنا بنختبر الخانة اللي شاكين فيها وهي password. هتبعت الطلب ده وتقارن النتيجة:

  • لو النتيجة رجعت شبه الاستجابة رقم 1 (نجاح): يبقى مبروك، الخانة دي موجودة فعلاً واسمها password، وتقدر تبدأ تخمن طولها وحروفها.

  • لو النتيجة رجعت شبه الاستجابة رقم 2 (فشل): يبقى الخانة دي مش موجودة، والمبرمج مسمي الباسورد اسم تاني.

إيه الحل لو الخانة مش اسمها Password؟ (Dictionary Attack)

السطر الأخير في النص بيقولك إنك مش هتقعد تجرب الأسماء التانية بإيدك. لو طلعت مش موجودة، هتروح على برنامج Burp Suite (Intruder)، وتحط كلمة password كـ Payload Position كده: admin' && this.§password§!='

وتقوم ضايف قائمة كلمات (Wordlist) مليانة أسماء مشهورة للخانات (زي pwd, pass, token, secret, hash). الـ Intruder هيبدأ يبدل الكلمات ويبعت الطلبات، وأول ما تلاقي طلب رجع لك استجابة حجمها مختلف وشبه الـ True Baseline، يبقى أنت كده اصطدت اسم الخانة الصحيح!

الخلاصة للـ Blind Injection، ترتيب شغلك كمخترق بيكون كده:

  1. تكتشف اسم الخانة: (الخطوة دي).

  2. تكتشف طول البيانات جوه الخانة: (بخاصية .length اللي شرحناها المرة اللي فاتت).

  3. تستخرج البيانات حرف حرف: (باستخدام [0] == 'a').


Exploiting NoSQL operator injection to extract data :

يا بطل، إنت كده دخلت في "ليفل الوحش" في اختراق الـ NoSQL! الجزء ده بينقلك من مرحلة التخمين لمرحلة السيطرة الكاملة على قاعدة البيانات.

النص ده بيحل مشكلتين كبار قابلونا في الخطوات اللي فاتت:

  1. إيه العمل لو الموقع مش بيستخدم دالة $where أصلاً في الكود بتاعه؟

  2. إيه العمل لو جربنا نخمن اسم خانة الباسورد بـ Wordlist (زي ما شرحنا المرة اللي فاتت) ومفيش ولا كلمة نفعت؟

تعالى نشرح الحلين بالتفصيل:

1. حقن معامل $where بنفسك (Injecting Operators)

في الأمثلة اللي فاتت، كنا بنكسر الكود لأن المبرمج كان كاتب دالة $where من عنده. هنا بقى، المبرمج كاتب كود عادي جداً ومفيش أي جافاسكريبت، بس إحنا اللي هنجبر قاعدة البيانات تنفذ كود جافاسكريبت!

بما إننا بنبعت البيانات في شكل JSON، إحنا نقدر نضيف معاملات (Operators) من عندنا كأنها جزء من الطلب.

إزاي نختبر ده؟ (اختبار الصح والغلط) هتبعت طلبين للسيرفر عشان تشوف هو بينفذ أوامرك ولا بيتجاهلها:

  • الطلب الأول (شرط خاطئ):

    JSON

    {"username":"wiener","password":"peter", "$where":"0"}

    أنت هنا بتضيف $where وبتحط قيمتها 0 (اللي هي في البرمجة معناها False). لو السيرفر نفذ الكود ده، الشرط هيكون غلط فمش هيسجل دخولك (أو هيرجع Error).

  • الطلب الثاني (شرط صحيح):

    JSON

    {"username":"wiener","password":"peter", "$where":"1"}

    هنا حطيت 1 (اللي معناها True). لو السيرفر نفذ الكود وسجل دخولك بنجاح.. يبقى مبروك! إنت كده اكتشفت إن السيرفر بياخد الـ $where بتاعتك وينفذها، وبقى معاك صلاحية تنفذ أي كود جافاسكريبت على قاعدة البيانات (Remote Code Execution بشكل مصغر).

2. استخراج أسماء الخانات بدقة (Extracting Field Names)

المرة اللي فاتت قلنا لو مش عارفين اسم خانة الباسورد، بنخمنها بقائمة كلمات (Dictionary Attack). بس التخمين بياخد وقت وممكن يفشل. النص ده بيديك الحل السحري: خلي قاعدة البيانات هي اللي تنطق وتقولك أسماء خاناتها إيه!

بما إننا خلاص أثبتنا إننا نقدر نشغل جافاسكريبت بالـ $where، هنستخدم دالة في الجافاسكريبت اسمها Object.keys(). الدالة دي وظيفتها إنها تجيب كل أسماء الخانات اللي جوه السجل وتحطهم في قائمة (Array).

تحليل كود الاستخراج:

JSON

"$where":"Object.keys(this)[0].match('^.{0}a.*')"

تعالى نفصص الكود ده حتة حتة:

  • this: بتشير للسجل الحالي (مثلاً بيانات المستخدم wiener).

  • Object.keys(this): بتجيب أسماء الخانات، يعني النتيجة هتبقى كده مثلاً: ["_id", "username", "password", "role"].

  • [0]: بتختار أول خانة في القائمة (اللي هي هنا _id). لو خليتها [1] هتجيب username وهكذا.

  • .match('^.{0}a.*'): ده كود Regex عبقري بيسأل قاعدة البيانات سؤال محدد جداً:

    • ^: ابدأ من أول الكلمة.

    • .{0}: اتخطى صفر من الحروف (يعني خليك عند الحرف الأول). لو بقت .{1} يعني اتخطى أول حرف وركز مع الحرف التاني.

    • a: هل الحرف ده هو 'a'؟

    • .*: وبعده أي حروف تانية مش مهم.

الخلاصة العملية للـ Payload ده: أنت بتقول للسيرفر: "سجل دخولي لو كان الحرف الأول من اسم الخانة الأولى هو حرف الـ a".

  • لو سجل دخول: يبقى اسم الخانة بيبدأ بحرف a.

  • لو رفض: يبقى بيبدأ بحرف تاني، وتقوم رايح للـ Intruder في Burp Suite وتخليه يجرب باقي الحروف.

لما تخلص الحرف الأول، تغير .{0} تخليها .{1} عشان تخمن الحرف التاني، وهكذا لحد ما تجمّع اسم الخانة بالكامل (مثلاً تكتشف إن اسمها secret_token بدل password). وبعد ما تكتشف اسمها، ترجع للدرس اللي فات وتستخرج البيانات اللي جواها!


Exfiltrating data using operators:

الدرس ده بيشرح الـ "Plan B" أو الخطة البديلة لأي Penetration Tester لما بيلاقي السيرفر متأمن بزيادة.

في الدروس اللي فاتت، كنا بنعتمد على إننا نكتب كود جافاسكريبت (زي الـ $where) وقاعدة البيانات بتنفذه. لكن إيه العمل لو المبرمج كان ذكي وقفل خاصية تنفيذ الجافاسكريبت تماماً؟ هل كده نقف؟ أكيد لأ.

هنا بييجي دور استخراج البيانات باستخدام الـ Operators العادية (المعاملات الأصلية بتاعة MongoDB)، وأهم وأخطر معامل فيهم هو $regex (التعبيرات النمطية).

تعالى نفصص التكنيك ده خطوة بخطوة:

1. مرحلة الفحص والتأكد (Vulnerability Check)

أول حاجة لازم تعملها هي إنك تتأكد هل السيرفر بيقبل الـ $regex أصلاً ولا بيعمله Block. عشان كده هتبعت الـ Payload الأولاني ده:

JSON

{"username":"admin","password":{"$regex":"^.*"}}

إيه معنى كود الـ Regex ده؟

  • ^ : معناها "ابحث من بداية الكلمة".

  • .* : معناها "أي عدد من أي حروف".

  • الترجمة الحرفية: "سجل دخولي لو كان الباسورد بيبدأ بأي حاجة".

بما إن أي باسورد في الدنيا "بيبدأ بأي حاجة"، فالشرط ده دايماً (True). لو الموقع رد عليك برد إيجابي (عمل تسجيل دخول أو جاب استجابة مختلفة عن رسالة الباسورد الغلط)، يبقى إنت كده اتأكدت إن ثغرة الـ Regex Injection موجودة وشغالة.

2. مرحلة استخراج البيانات حرف بحرف (Data Exfiltration)

بما إن الثغرة شغالة، هنبدأ نسأل قاعدة البيانات أسئلة محددة جداً عشان نسرق الباسورد. هتبعت الـ Payload التاني ده:

JSON

{"username":"admin","password":{"$regex":"^a.*"}}

إيه اللي اتغير هنا؟ إحنا حطينا حرف الـ a بعد علامة البداية ^.

  • الترجمة الحرفية: "سجل دخولي لو كان الباسورد بيبدأ بحرف الـ a، وبعده أي حاجة".

هتقرأ الاستجابة إزاي؟

  • لو السيرفر عملك تسجيل دخول: يبقى مبروك، أول حرف من باسورد الأدمن هو a!

  • لو السيرفر رفض وقالك الباسورد غلط: يبقى الحرف الأول مش a، ولازم تجرب الحرف اللي بعده b، وبعده c، وهكذا.

إزاي تطبق ده عملي بسرعة؟ (The Hacker Way)

أكيد مش هتقعد تجرب الحروف كلها بإيدك. هتروح لـ Burp Suite وتبعث الطلب ده للـ Intruder.

الخطوة الأولى (اكتشاف الحرف الأول): هتحدد الحرف الأول كـ Payload Position بالشكل ده: {"username":"admin","password":{"$regex":"^§a§.*"}} وتخلي الـ Intruder يجرب كل الحروف والأرقام (a-z, 0-9). الطلب الوحيد اللي هيرجع استجابة مختلفة (زي 302 Redirect)، هيكون هو الحرف الصح.

الخطوة التانية (اكتشاف الحرف التاني): لنفترض إن الـ Intruder اكتشف إن أول حرف هو m. هترجع تعدل الـ Request بتاعك وتثبت الـ m، وتحط الـ Payload Position في الحرف التاني بالشكل ده: {"username":"admin","password":{"$regex":"^m§a§.*"}}

وهكذا، تفضل تصطاد الحروف واحد ورا التاني لحد ما تجمّع باسورد الأدمن بالكامل وتخترق الحساب. التكنيك ده بطيء شوية (عشان كده اسمه Blind Injection)، لكنه فتاك ومبيحتاجش جافاسكريبت خالص!


Timing based injection:

إيه المشكلة اللي بتحلها الطريقة دي؟ (The Black Hole)

في الدروس اللي فاتت، كنا بنعتمد على إن السيرفر بيرد علينا باستجابة مختلفة (مثلاً: بيسجل دخول، بيرجع Error 500، أو حجم الصفحة بيتغير). لكن إيه العمل لو السيرفر متأمن لدرجة إنه بيرجع نفس الرد بالضبط في كل الحالات؟ (يعني سواء الباسورد صح، غلط، أو الكود فيه مشكلة، السيرفر بيرجعلك دايماً صفحة مكتوب فيها "Invalid Login" بنفس الحجم وبنفس الـ Status Code).

هنا، إحنا فقدنا ميزة الرؤية تماماً (Total Blindness). وعشان نعرف إجابة أسئلتنا (True or False)، بنلجأ لسلاح "الوقت". بنقول لقاعدة البيانات: "لو إجابتي صحيحة، أقفلي السيرفر وماترديش عليا لمدة 5 ثواني.. ولو غلط ردي عليا فوراً".

تعالى نفصص الأكواد المكتوبة:

1. مرحلة جس النبض (Baseline Test)

JSON

{"$where": "sleep(5000)"}

أول حاجة بتعملها هي إنك بتجرب تبعت طلب عادي وتحسب بياخد كام مللي ثانية عشان يرد (مثلاً 200ms). بعدين تبعت الكود ده، اللي بيأمر السيرفر ينام (sleep) لمدة 5000 مللي ثانية (5 ثواني). لو السيرفر اتأخر فعلاً 5 ثواني في الرد، يبقى إنت اتأكدت 100% إنك تقدر تنفذ Time-based Injection.

2. استخراج الباسورد بالوقت (الـ Payloads المعقدة)

النص بيقدم لك طريقتين عشان تختبر بيهم أول حرف من الباسورد بناءً على الوقت:

الطريقة الأولى: استخدام sleep()

JavaScript

admin'+function(x){if(x.password[0]==="a"){sleep(5000)};}(this)+'
  • الشرح: أنت هنا حقنت دالة جافاسكريبت بتسأل: "لو الباسورد بتاع الأدمن أول حرف فيه هو 'a'، نفذ أمر sleep(5000)".

  • النتيجة: لو الرد اتأخر 5 ثواني، إذن الحرف هو a. لو الرد جه بسرعة، إذن الحرف مش a، وتجرب اللي بعده.

الطريقة التانية: حرق المعالج (The CPU Burner)

JavaScript

admin'+function(x){var waitTill = new Date(new Date().getTime() + 5000);while((x.password[0]==="a") && waitTill > new Date()){};}(this)+'

ليه الكود ده معقد كده؟ لأن في بعض الأحيان، مطورين MongoDB بيعملوا تعطيل (Disable) لدالة sleep() عشان يمنعوا الهجمات دي. فبنضطر نستخدم حيلة برمجية بالـ while loop.

  • الشرح: الكود ده بيعمل الآتي:

    1. بيسجل الوقت الحالي، ويزود عليه 5 ثواني لقدام ويحفظه في المتغير waitTill.

    2. بيدخل في حلقة مفرغة (Loop): "طول ما الحرف الأول هو 'a'، وطول ما الوقت الحالي لسه موصلش للوقت اللي حفظناه.. أفضل لف في دوامة فاضية وماتعملش حاجة".

  • النتيجة: الكود ده بيجبر الـ CPU بتاع السيرفر يفضل يلف في مكانه لمدة 5 ثواني كاملة لو الشرط صحيح، وبكده بنحقق نفس نتيجة الـ sleep() بالظبط حتى لو الدالة نفسها مقفولة!

إزاي تنفذها في Burp Suite كالمحترفين؟

بما إن الـ Status Code والـ Length مش هيتغيروا، الـ Intruder مش هيعرف يفرق بين الحروف الصح والغلط بالطريقة العادية. التريكة هنا: لما تبعت الهجوم للـ Intruder، لازم تروح لقائمة Columns (في نافذة النتايج)، وتضيف عمود اسمه Response received أو Response completed (واللي بيحسب الوقت بالمللي ثانية). ولما الهجوم يخلص، ترتب النتايج تصاعدياً أو تنازلياً بناءً على عمود الوقت. الطلب الوحيد اللي هتلاقي وقته كسر الـ 5000، هو ده الحرف الصح بتاعك!