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
- مثال على الحصول على معلومات لا يمكننا الوصول إليها في الطبيعي :
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 هم :
- Union Based SQLi
- Blind SQLi
- Second-order SQLi (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
- كل جملة SELECT يجب أن تحتوي على نفس عدد الأعمدة (Columns)
- كل عمود يجب أن يقابله عمود بنفس النوع, يعني لو نوعه 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.
هناك طريقتان لمعرفة عدد الأعمدة
- الطريقة الأولى تدور حول حقن سلسلة من جملة 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
يدل على أن الرقم أكبر من عدد الأعمدة الموجودة, بالتالي نكون قد علمنا عدد الأعمدة ألا وهو أكبر رقم لم يتسبب في خطأ. - الطريقة الثانية هي أن نحقن سلسلة من
UNION SELECT NULL '
كالتالي
كود:' UNION SELECT NULL-- ' UNION SELECT NULL,NULL-- ' UNION SELECT NULL,NULL,NULL--
ملحوظة: نستخدم كلمة 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'--
بعد أن حددنا عدد الأعمدة والعمود المناسب لاستقبال البيانات نصل إلى آخر خطوة ألا وهي استخراج البيانات.إذاً - افترضنا أنه يوجد جدول يسمى
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'
TrackingId
موجودة ومعرّفة بالفعل يقوم التطبيق بكتابة "Welcome back" في الرد.هذا الاختلاف وحده كافي لاستخراج البيانات بناءاً على صحة الأمر.
لجعل الأمر أبسط, تعالوا نتخيل أنه لدينا طلبان للموقع وأننا سنغير قيمة ال Cookie كالتالي:
كود:
…xyz' AND '1'='1
…xyz' AND '1'='2
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
في المثال الأول لا تقوم قاعدة البيانات بإحداث أي خطأ أو تغيير في الرد من التطبيق, أما في المثال الثاني فيحدث خطأ القسمة على صفر لأن الشرط صحيح أي أن
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'--
مثال على استخراج البيانات بتلك الطريقة:
كود:
'; 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);
كود:
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();


التعديل الأخير بواسطة المشرف: