مضى على الشبكة و يوم من العطاء.
  • تحذير: يجب على كل روّاد الشبكة تشغيل برامج الاختراق داخل الأنظمة الوهمية وهي بهدف التعلم وحماية الأعضاء والتوعية بها

شرح ثغرة SQLi وكيفية كشفها وطريقة منعها عند التطوير

  • بادئ الموضوع بادئ الموضوع Mr_Code
  • تاريخ البدء تاريخ البدء

Mr_Code

مشرف سابق

السمعة:

🌿بسم الله والصلاة والسلام على رسول الله🌿
في هذا المنشور رح نتعلم بإذن الله :
  • ما هي ثغرة ال SQL injection
  • كيفية الكشف عن ثغرة SQL injection بأنواعها المختلفة في تطبيقات الويب
  • كيف نتجنب ثغرة ال SQL injection عند تطوير المواقع

ما هي ثغرة SQL injection
هي ثغرة في تطبيقات الويب تمّكن المهاجم من التلاعب بالأمور (queries) التي يرسلها التطبيق إلى قاعدة البيانات الخاص به (Database)، وبذلك يتمكن المهاجم أن يستعرض بيانات حساسة قد تنتمي إلى مستخدمين آخرين أو أي بيانات يمكن للتطبيق أن يصل إليها.
حتى أنه يمكنه التلاعب في هذه البيانات والتعديل عليها أو حذفها بالكامل؛ مما يسبب ضرر بالتطبيق أو حتى تغيير بطريقة تشغيله.
في بعض الأحيان تمّكن ثغرة ال Sql injection المهاجم من الأستحواذ بشكل كامل علي البنية التحتية الخلفية (Back-end infrastructure) للتطبيق أو حتى شن هجوم DDos (Denial of service)

كيفية الكشف عن ثغرة ال SQLi

  • إدخال علامة تنصيص واحده ' والبحث عن أي تغيير في طريقة عمل التطبيق أو أي شيء غريب.
  • أمر خاص بلغة SQL والذي من شأنه أن يجعل الأمر الأصلي يعمل بشكله العادي, ثم تعديل القيمة والبحث عن أي تغيير ملحوظ.
  • إدخال شرط منطقي (Boolean condition) مثل OR 1=1 و OR 1=2 والبحث عن أي تغيير في رد التطبيق.
  • أمر SQL من شأنه أن يتسبب في تأخير الرد من التطبيق والبحث عن أي تغيير في زمن الرد.

أين تظهر ثغرة SQLi
في أغلب الأحيان تكون ثغرة SQLi في جملة ال Where الخاصة بجملة ال Select في أمر ال SQL
يمكن أيضاً أن تظهر في الأماكن الآتيه:
  • في القيم المعدلة في جملة Updated أو في جملة ال Where الخاصة بها
  • في القيم المضافة في جملة INSERT
  • في جملة Select في اسم العمود أو الجدول
  • في جملة Select في جملة ال ORDER BY

كيف نستخدم ثغرة SQLi
تستخدم ثغرة SQLi بشكل أساسي في :
  • الحصول علي معلومات مهمة من قاعدة البيانات Database مثل كلمات المرور أو معلومات عن مستخدمين مثل بطاقات ائتمان أو معلومات لا يمكننا الوصول إليها في الطبيعي أو غيره.
  • التلاعب بمنطق التطبيق بحيث يمكننا الدخول إلى أي حساب بدون كلمة سر أو شراء منتجات دون الدفع أو غيره.

أمثلة على SQLi
  1. مثال على الحصول على معلومات لا يمكننا الوصول إليها في الطبيعي :
إذا كان عندنا رابط موقع يحتوي على ثغرة SQLi كالتالي https://insecure-website.com/products?category=Gifts
وإذا افترضنا أنه عند طلب هذا الرابط فأن هذا الأمر يقوم التطبيق بتنفيذه حتى يعرض المنتجات إلى المستخدمين
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
فأنه يمكننا أن نرى أن هذا الأمر في SQL يقوم باستدعاء كل * المنتجات من جدول products حيث أن الصنف يساوي = Gifts وأن هذا المنتج قد تم نشره بالفعل released = 1
فإذا افترضنا أن المنتجات التي لم يتم عرضها بعد تحتوي على قيمة released = 0 وأنه لا يوجد أي حماية ضد ثغرة SQLi فأنه بإمكاننا إدخال التالي عند طلب الرابط في المتصفح
--'https://insecure-website.com/products?category=Gifts
فإن التطبيق سوف ينفذ الأمر التالي في قاعدة البيانات الخاص به
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
وبما أننا نعرف مسبقًا عند دراسة لغة SQL أنه علامة ال -- تعني comment بالتالي فأنه أي شيء بعدها سيتم معاملته على أنه ليس جزء من الأمر وسيتم إهماله.
وبهذا سيقوم التطبيق بعرض كل المنتجات بدون النظر إلى الشرط AND released = 1 وبالتالي حتى المنتجات التي لم يتم نشرها حتى الآن سيتم أيضاً عرضها.


2. مثال على التلاعب بمنطق التطبيق :
إذا تخيلنا تطبيق يقوم بالسماح للمستخدمين إلى الدخول بعد إدخال اسم المستخدم و كلمة المرور فنتخيل أن التطبيق يقوم بتنفيذ الأمر التالي عند إدخال اسم مستخدم (wiener) وكلمة سر (bluecheese)
'SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese
وإذا قام الأمر بإرجاع أي بيانات للمستخدم سيتم حينها الدخول إلى التطبيق.
فإذا قمنا بإدخال اسم المستخدم كالتالي --'administrator مع كلمة مرور فارغة فسيكون الأمر المنفذ كالتالي:
'' = SELECT * FROM users WHERE username = 'administrator'--' AND password
وبذلك فسيتم تجاهل شرط كلمة السر وبالتالي سيتم الدخول بنجاح.


أنواع SQLi
أشهر أنواع ثغرة SQLi هم :
  1. Union Based SQLi
  2. Blind SQLi
  3. Second-order SQLi (Stored SQLi)
أول نوعين وهما الأشهر وسيتم شرحهما بالتفصيل باذن الله.النوع الثالث وهو عكس أول نوعين حيث أنهما يعتمدان على أن أمر ال SQL المدخل يتم تنفيذه بالحال عند الإدخال, أما ال (Stored SQLi) فتعتمد على أن الأمر يتم تخزينه في قاعدة البيانات بطريقة غير آمنة
وبعد ذلك يتم استخدامه عند إجراء طلب آخر في التطبيق, وبذلك فإن الثغرة تظهر فقط عند تنفيذ الطلب الثاني وليس عند إدخال الأمر على الفور.


Union Based SQLi
في حالة كان التطبيق مصاب ب SQLi وكانت نتيجة الأمر تظهر لنا مباشرة على الصفحة عند التنفيذ فإنه يمكننا استخدام كلمة UNION كي ندمج نتائج أول أمر مع أمر آخر من اختيارنا.
مثلًا, لو كان التطبيق ينفذ الأمر التالي عند عرض المنتجات إلى المستخدمين 'SELECT name, description FROM products WHERE category = 'Gifts
فبإمكاننا إدخال الأمر التالي بعد اسم الصنف كالتالي --UNION SELECT username, password FROM users '
ينتج عن ذلك الأمر أن التطبيق سوف يقوم بإرجاع أسماء المنتجات ووصفها بالإضافة إلى أسماء المستخدمين وكلمات المرور الخاصة بهم.
نفهم من ذلك أن استخدام كلمة UNION تضيف جملة SELECT أو أكثر وتعيد النتائج بعد ذلك من كل جملة SELECT مضافة إلى بعضها.

هناك شرطين أساسيين حتى يتم تنفيذ جملة UNION
  1. كل جملة SELECT يجب أن تحتوي على نفس عدد الأعمدة (Columns)
  2. كل عمود يجب أن يقابله عمود بنفس النوع, يعني لو نوعه int فأن العمود المقابل في كل جملة SELECT يجب أن يكون من نفس النوع.
كي توضح فكرة النقطة الثانية, إذا كان عندنا أمر مثل : SELECT col_1, col_2 from table_1 UNION SELECT col_3,col_4 from table_2
فإنه يجب أن يكون العمود الأول col_1 من نفس نوع col_3 وكذلك الأمر مع col_2 و col_4 وكذلك نلاحظ أن كل جملة SELECT يوجد بها نفس العدد من الأعمدة 2 في هذا المثال.
وبذلك فأن أول شيء نهتم به عند وجود Union SQLi هو معرفة عدد الأعمدة و العمود المناسب لاستقبال بيانات من نوع String.

هناك طريقتان لمعرفة عدد الأعمدة

  1. الطريقة الأولى تدور حول حقن سلسلة من جملة ORDER BY والتي بدورها تقوم بترتيب النتائج بناءًا على رقم أو اسم عمود معين.
    فإذا كانت نقطة الحقن هي بعد كلمة Where فيمكننا إدخال سلسلة من ORDER BY كالتالي:

    كود:
    ' ORDER BY 1--
    ' ORDER BY 2--
    ' ORDER BY 3--
    ونظل بزيادة رقم العمود حتى يحدث خطأ من قاعدة البيانات كالتالي .The ORDER BY position number 3 is out of range of the number of items in the select list
    يدل على أن الرقم أكبر من عدد الأعمدة الموجودة, بالتالي نكون قد علمنا عدد الأعمدة ألا وهو أكبر رقم لم يتسبب في خطأ.
  2. الطريقة الثانية هي أن نحقن سلسلة من UNION SELECT NULL ' كالتالي
    كود:
    ' UNION SELECT NULL--
    ' UNION SELECT NULL,NULL--
    ' UNION SELECT NULL,NULL,NULL--
    ونكرر العملية بزيادة عدد ال NULL في كل مرة حتى يظهر لنا خطأ من قاعدة البيانات أو تغيير ملحوظ في رد التطبيق كإختفاء رسالة مثل 'Welcome Back'.
    ملحوظة: نستخدم كلمة NULL لأنها قابلة للتحويل إلى أي نوع آخر سواء كان int أو string إلى آخره.
    ملحوظة: في قاعدة بيانات Orcale يجب استخدام From table_name لكل جملة SELECT فيوجد جدول في قاعدة البيانات مدمج معها يمكننا استخدامه في هذا الأمر كالتالي
    كود:
    ' UNION SELECT NULL FROM DUAL--

    الخطوة الثانية هي إيجاد عمود من نوع String حتى يمكننا استرجاع معلومات من قاعدة البيانات من خلاله
    يمكننا اختبار كل عمود على حدى عن طريق حقن مجموعة من الاختبارات وإذا قام الأمر بالتنفيذ بدون مشكلة وتم إرجاع قيمة إضافية في رد التطبيق ألا وهي القيمة المختارة في الحقن
    فبذلك نكون قد علمنا العمود المناسب للحقن
    كود:
     ' UNION SELECT 'a',NULL,NULL,NULL--
    ' UNION SELECT NULL,'a',NULL,NULL--
    ' UNION SELECT NULL,NULL,'a',NULL--
    ' UNION SELECT NULL,NULL,NULL,'a'--

    بعد أن حددنا عدد الأعمدة والعمود المناسب لاستقبال البيانات نصل إلى آخر خطوة ألا وهي استخراج البيانات.إذاً
  3. افترضنا أنه يوجد جدول يسمى users ويوجد به عمودان username , password فإنه يمكننا استخراج البيانات عند حقن التالي:
    كود:
    ' UNION SELECT username, password FROM users--
    آخر شيء هو استرجاع أكثر من قيمة في عمود واحد
    نقوم بإسترجاع أكثر من قيمة في عمود واحد عندما لا يوجد إلا عمود واحد من نوع String ونقوم بذلك عن طريق ما يسمى ب String Concatenation أو إضافة أكثر من قيمة من نوع String إلى بعضهم.
    طريقة الإضافة تختلف على حسب نوع قاعدة البيانات, مثلًا على Orcale نقوم بحقن التالي:

    كود:
    ' UNION SELECT username || '~' || password FROM users--
    نلاحظ هنا استخدام علامة || لإضافة أكثر من قيمة واستخدمنا كذلك رمز ~ حتى نستطيع التفرقة بين القيم username, password.


Blind SQLi
نوع ال Blind SQLi يحدث عندما توجد ثغرة SQLi بالتطبيق و لكن الرد لا يحتوي على أي معلومات خاصة بأمر ال SQL الذي تم تنفيذه أو أي خطأ من قاعدة البيانات.
وبما أن الرد لا يحتوي على بيانات من الأمر إذا فالطرق المستخدمة مع نوع Union Based SQLi لن تجدي نفعاً هنا, بالتالي يجب علينا استخدام أساليب أُخرى.

Exploiting blind SQL injection by triggering conditional responses

اذا تخيلنا تطبيق يستخدم Cookie: TrackingId=u5YD3PapBcR4lN3e7Tj4 Cookie حتى يجمع بيانات عن المستخدمين وتحليلها, وعند معالجة هذه البيانات فإن التطبيق يقوم بتنفيذ أمر ال SQL التالي
كود:
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = 'u5YD3PapBcR4lN3e7Tj4'
في هذه الحالة توجد ثغرة SQLi لكن نتيجة الأمر لا تعود الينا في الرد, ومع ذلك يوجد اختلاف في طبيعة التطبيق, فعندما تكون قيمة ال TrackingId موجودة ومعرّفة بالفعل يقوم التطبيق بكتابة "Welcome back" في الرد.
هذا الاختلاف وحده كافي لاستخراج البيانات بناءاً على صحة الأمر.
لجعل الأمر أبسط, تعالوا نتخيل أنه لدينا طلبان للموقع وأننا سنغير قيمة ال Cookie كالتالي:
كود:
…xyz' AND '1'='1
…xyz' AND '1'='2
في الطلب الأول سيقوم التطبيق بطباعة "Welcome back" لأن الشرط صحيح 1=1 أما في الطلب الثاني لن يحتوي الرد على "Welcome back" وبذلك نتنبأ بأن الشرط غير صحيح لأن 1=2 ينتج عنه false .
بذلك المنطق يمكننا أن نتستنج صحة أي أمر ندخله وأن نستخرج المعلومات مرة تلو الأخرى.
فالنفترض أن قاعدة البيانات تحتوي على جدول users وأنه يوجد مستخدم باسم administrator فإذا أدخلنا التالي
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm
يمكننا أن نلاحظ أن رسالة "Welcome back" توجد بالرد وبذلك نعلم أن أول حرف من كلمة المرور هي أكبر من m
أي أنها تترواح بين (n,o,p,q,r,s,t,u,v,w,x,y,z)بعدها نقوم بإدخال xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) = 's
ونرى أن الرد يحتوي أيضاً على رسالة "Welcome back" مما يعني أن أول حرف هو s .
نقوم بتكرار العملية حتى نتمكن من استخراج كلمة المرور بالكامل.

Error-based SQL injection

تكملة على ال Blind SQLi, يوجد عندنا نوع أخر يعتمد على ال Errors الخاصة بقواعد البيانات في استخراج البيانات وهو معتمد على نوع قاعدة البيانات ونوع ال Error.

Exploiting blind SQL injection by triggering conditional errors

هذا النوع يختلف عن ال Conditional responses لأن التطبيق لا يحدث أي تغيير ملحوظ عند تنفيذ الأمر, ولكن في هذا النوع يمكننا استنتاج صحة الأمر بناءاً على ما إذا كان التطبيق يحدث أي خطأ و Error.
تكملة على مثال ال Cookie السابق, إذا قمنا بإدخال الأمرين التاليين :

كود:
xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a
نستخدم هنا كلمة CASE حتى نتمكن من معرفة صحة شرط معين ويكون هذا الشرط بعد كلمة When.
في المثال الأول لا تقوم قاعدة البيانات بإحداث أي خطأ أو تغيير في الرد من التطبيق, أما في المثال الثاني فيحدث خطأ القسمة على صفر لأن الشرط صحيح أي أن 1=1 .
وبنفس المنطق يمكننا استخراج قطعة تلو الأخرى من البيانات كالتالي:
كود:
xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a

Extracting sensitive data via verbose SQL error messages

هذا النوع لا يعتبر في الحقيقة Blind SQLi لأن التطبيق يقوم بطباعة الخطأ التي تسبب فيه الأمر المنفذ, بالتالي يصبح الأمر واضح لنا و يمكننا استخراج البيانات مباشرة بسببه.
مثال على خطأ تم إرجاعه في الرد عند إضافة ' (Single Quote) في باراميتر id
Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected char
وهنا نرى الأمر بشكل كامل, بالتالي يمكننا معرفة مكان الحقن وكيفية التلاعب بالأمر حتى نستخرج بيانات.
مثال على ذلك عندما نريد استخراج كلمة المرور يمكننا استخدام ()Function CAST كطريقة لإحداث خطأ عن عمد لنتمكن من تسريب كلمة السر كالتالي:
CAST((SELECT example_column FROM example_table) AS int)
ولأنه في الأغلب البيانات التي نريد استخراجها من نوع String فإن محاولة تغيير نوعها إلى int سيقوم بإحداث ال Error التالي:
"ERROR: invalid input syntax for type integer: "Example data

Exploiting blind SQL injection by triggering time delays

في النوع الأخير من ال Blind SQLi لا يقوم التطبيق بإرجاع أي خطأ من قاعدة البيانات ولا حتى أي تغيير ملحوظ, بالتالي لا يوجد أي طريقة حتى نتمكن من استخراج البيانات أو معرفة كيفية تنفيذ الأمر إلا استخدام الوقت كدليل على صحة تنفيذ الأمر وذلك لأن أوامر SQL يتم تنفيذها لحظياً عند إرسال الطلب إلى التطبيق, بالتالي أي تأخير في تنفيذ الأمر سيؤخر كذلك الرد من التطبيق.
الطرق المتبعة لتنفيذ time delay تعتمد على نوع قاعدة البيانات, فمثلًا على Microsoft SQL Server يمكننا استخدام الأمر التالي :
كود:
'; IF (1=2) WAITFOR DELAY '0:0:10'--
'; IF (1=1) WAITFOR DELAY '0:0:10'--
في الأمر الأول لن يحدث أي تغيير في زمن الرد, أما في الأمر الثاني سيتأخر الرد بمقدار عشر ثواني تقريباً عن الأمر الأول, مما يدل على أنه يوجد بالفعل ثغرة SQLi ويمكننا استخدام الوقت لتحديد صحة الشرط المنفذ بالأمر.
مثال على استخراج البيانات بتلك الطريقة:
كود:
'; IF (SELECT COUNT(Username) FROM Users WHERE Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') = 1 WAITFOR DELAY '0:0:{delay}'--


كيف نتجنب ثغرة SQLi عند تطوير مواقع الويب
بكل بساطة يمكننا استخدام ما يعرف ب Parameterized query أو Prepared Statements
فإذا كانت الثغرة عند استخدام String Concatenation أو إضافة المدخلات إلى الأمر مباشرة في مثل الكود الأتي:

كود:
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
فإنه بإمكاننا استخدام ال Parameterized query كالتالي:
كود:
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
بالتالي لن يحدث أي إخلال بهيكل الأمر الأصلي.

🍁الحمد لله رب العالمين🍁
 
التعديل الأخير بواسطة المشرف:
كود يا فخم بارك الله فيك وجزاك الله كل خير على هذا الطرح الرائع
ننتظر جديدك دايماً يا حبيب​
 
كود يا فخم بارك الله فيك وجزاك الله كل خير على هذا الطرح الرائع
ننتظر جديدك دايماً يا حبيب​
آمين يارب وإياكم 💙
 
عاااش
ما شاء الله بالتوفيق
 
متوقعتش موضوع بالقوة دي والتنظيم دا، أحسنت الشرح والتوضيح يا كود، استمر يا وحش 🔥
 
عاش كود شرح حلو
 

آخر المشاركات

فانوس

رمضان
عودة
أعلى