SQL Injection هو نوع من الهجمات على قاعدة بيانات التطبيق، حيث يقوم المهاجم بحقن كود SQL ضار في معاملات الاستعلام، مما يمنحه وصولاً غير مصرح به إلى البيانات أو إمكانية تعديلها. وفقاً لـ OWASP (2025)، لا يزال SQL Injection أحد الثغرات الأمنية الحرجة القادرة على اختراق قاعدة البيانات بالكامل. حقن كود SQL يسمح للمهاجم بقراءة وتعديل وحذف السجلات، وفي بعض الحالات، الوصول إلى نظام تشغيل الخادم.
الوجبات الرئيسية
SQL Injection هي ثغرة أمنية تحدث عندما يقوم التطبيق ببناء استعلامات SQL عن طريق تسلسل السلاسل النصية مع بيانات المستخدم. يقوم المهاجم بتمرير سلسلة نصية مصممة خصيصاً في معامل الاستعلام تغير بنية أمر SQL. بدلاً من أن تكون بيانات، تصبح السلسلة المحقونة جزءاً من كود SQL، مما يسمح للمهاجم بتنفيذ استعلامات عشوائية ضد قاعدة البيانات. وفقاً لـ تقرير اختراقات البيانات من Verizon (2025)، فإن SQL Injection موجود في 8% من جميع اختراقات البيانات التي تم التحقيق فيها.
على الرغم من كونها ثغرة معروفة (ذُكرت لأول مرة في أواخر التسعينيات)، لا يزال SQL Injection موجوداً في التطبيقات الحديثة. السبب هو الخطأ البشري: يكتب المطورون كوداً بتسلسل السلاسل النصية، ولا يتم إعادة هيكلة الكود القديم، وتستخدم أطر ORM بشكل غير صحيح (مثلاً، استعلامات raw مع استيفاء السلاسل النصية). وفقاً لـ Veracode (2026)، حوالي 14% من جميع التطبيقات الممسوحة تحتوي على ثغرة SQLi واحدة على الأقل.
حقن SQL ناجح يمنح المهاجم مجموعة واسعة من الإمكانيات: قراءة أي جداول قاعدة البيانات، بما في ذلك تجزئة كلمات المرور والبيانات الشخصية للمستخدمين؛ تعديل وحذف السجلات؛ تنفيذ عمليات إدارية (DROP TABLE, TRUNCATE)؛ وفي بعض التهيئات، تنفيذ أوامر عن بعد عبر xp_cmdshell (MSSQL) أو INTO OUTFILE (MySQL). العواقب تتراوح من تسرب بيانات المستخدم إلى فقدان كامل للسيطرة على النظام.
تصنف حقن SQL حسب طريقة استخراج البيانات من قاعدة البيانات. يعتمد اختيار الطريقة على كيفية معالجة التطبيق لنتائج الاستعلام ورسائل الخطأ. تصنيف OWASP يحدد ثلاثة أنواع رئيسية: In-band (استخراج البيانات عبر نفس القناة)، Inferential/Blind (الاستدلال المنطقي)، وOut-of-band (نقل البيانات عبر قناة أخرى).
| النوع | طريقة استخراج البيانات | التعقيد | التكرار |
|---|---|---|---|
| In-band (كلاسيكي) | مباشرة عبر نتيجة الاستعلام | منخفض | مرتفع |
| Blind SQLi | الاستدلال المنطقي من استجابات الخادم | مرتفع | متوسط |
| Out-of-band | عبر قناة خارجية (DNS, HTTP) | متوسط | منخفض |
النوع الأكثر شيوعاً. يقوم المهاجم بحقن كود SQL في معامل استعلام، وتكون نتيجة الحقن مرئية مباشرة في استجابة الخادم. نوعان فرعيان: Error-based (عبر رسائل خطأ قاعدة البيانات) وUNION-based (عبر عامل UNION SELECT). Error-based يستخدم معلومات من رسائل الخطأ، مثل خطأ نحوي في MySQL قد يكشف اسم الجدول أو بنية الاستعلام. UNION-based يسمح بدمج نتائج الاستعلام الشرعي مع بيانات من جداول أخرى في قاعدة البيانات.
يستخدم عندما لا يعرض التطبيق نتائج الاستعلام أو رسائل الخطأ. يطرح المهاجم أسئلة بنعم/لا عن طريق إرسال استعلامات بشروط منطقية وتحليل الاختلافات في استجابات الخادم (مثلاً، وقت الاستجابة أو محتوى الصفحة). Time-based Blind SQLi يستخدم دوال التأخير (SLEEP, WAITFOR DELAY) لتأكيد الشروط — إذا استغرق تحميل الصفحة وقتاً أطول، يكون الشرط صحيحاً. هذه الطريقة بطيئة جداً — استخراج سجل واحد قد يستغرق ساعات.
# مثال على Blind SQL Injection (قائم على الوقت)
# إذا كان SQLi ضعيفاً، SLEEP(2) ينفذ إذا تحقق الشرط
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# إذا وصل الرد بعد >2 ثانية — فإن الحرف الأول من كلمة المرور هو 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
لا يتم نقل البيانات عبر استجابة HTTP ولكن عبر قنوات بديلة: استعلامات DNS، طلبات HTTP إلى خادم خارجي، SMTP. يستخدم عندما لا يعيد التطبيق نتائج الاستعلام أو لا يعرض الأخطاء. MySQL تدعم دالة LOAD_FILE() التي يمكنها بدء استعلام DNS، وMSSQL لديها xp_dirtree لإرسال البيانات إلى خادم SMB بعيد. Out-of-band SQL Injection فعال لكنه يتطلب إعدادات إضافية لخادم المهاجم ودوال محددة في قاعدة البيانات.
آلية SQL Injection تعتمد على أن SQL يستخدم علامات الاقتباس للنصوص الحرفية. إذا أدخل التطبيق إدخال المستخدم مباشرة في استعلام SQL دون تهريب، يمكن للمهاجم «إغلاق» النص وإضافة كود SQL عشوائي. على سبيل المثال، في الاستعلام SELECT * FROM users WHERE name = '$input'، إدراج ' OR '1'='1 يحوله إلى SELECT * FROM users WHERE name = '' OR '1'='1'، مما يعيد جميع المستخدمين.
لنفكر في نموذج تسجيل دخول مع الاستعلام SELECT * FROM users WHERE username = '$user' AND password = '$pass'. إذا أدخل المهاجم admin' -- في حقل اسم المستخدم وترك كلمة المرور فارغة، يصبح الاستعلام الناتج SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. الرموز -- تعلق على بقية الاستعلام، مما يعطل التحقق من كلمة المرور. يعيد الخادم سجل المستخدم admin، ويدخل المهاجم إلى النظام دون معرفة كلمة المرور.
# مثال على SQL Injection — تجاوز المصادقة
# كود ضعيف: تسلسل مباشر للسلاسل النصية
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" يلغي التحقق من كلمة المرور
# إصدار آمن: استعلام موسوم
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
عامل UNION SELECT يسمح بدمج نتائج استعلامين SELECT. إذا وجد المهاجم معاملاً ضعيفاً، يضيف UNION SELECT مع استعلام من جدول آخر. مثال: ' UNION SELECT username, password FROM admins --. شرط النجاح هو أن عدد الأعمدة في كلا الاستعلامين يجب أن يكون متساوياً. يتم تحديد عدد الأعمدة عبر ORDER BY (إدراج ' ORDER BY 1--، ثم 2، 3... حتى يظهر خطأ). بمعرفة عدد الأعمدة، يدرج المهاجم UNION SELECT بنفس عدد الحقول.
تواجه التطبيقات المحمولة SQL Injection على جانب الخادم (API) وعلى جانب العميل — في قواعد البيانات المحلية (SQLite, Realm). بينما SQLi من جانب الخادم في واجهات API المحمولة مشابه لتطبيقات الويب، فإن قواعد البيانات المحلية تخلق ناقلاً إضافياً. إذا كان التطبيق يخزن البيانات في SQLite وينفذ استعلامات بتسلسل السلاسل النصية، فإن البيانات الضارة التي تدخل قاعدة البيانات المحلية عبر API قد تؤدي إلى SQLi أثناء المعالجة اللاحقة.
قاعدة بيانات SQLite المحلية على الجهاز عرضة أيضاً لـ SQL Injection إذا كان التطبيق يبني الاستعلامات بتسلسل السلاسل النصية. Content Providers على Android وCore Data على iOS يستخدمان الوسم افتراضياً، لكن الاستعلامات الخام تتطلب اهتمام المطور. SQLite لا تدعم استعلامات متعددة مفصولة بفاصلة منقوطة، مما يحد من خيارات المهاجم لكنه لا يحمي من قراءة البيانات عبر شروط WHERE. استخدم دائماً selectionArgs في Android وNSPredicate مع معاملات في iOS.
API الذي يتصل به التطبيق المحمول عرضة مثل أي خادم ويب. غالباً ما يفترض مطورو التطبيقات المحمولة أن SQLi هي مشكلة النهاية الخلفية فقط، لكن الثغرة تحدث في نقطة نهاية API التي تقبل معاملات من العميل. فصل المسؤوليات لا يحمي: إذا نسي مطور النهاية الخلفية وسم الاستعلام، يصبح تطبيق المستخدم المحمول ناقلاً للهجوم. اطلب من النهاية الخلفية استخدام ORM أو prepared statements.
// مثال على SQL Injection في SQLite المحلي على Android
// كود ضعيف: تسلسل مباشر
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// إصدار آمن: وسم عبر selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
الحماية من SQL Injection تقوم على مبدأ بسيط: لا تثق أبداً في إدخال المستخدم في استعلامات SQL. الطريقة الوحيدة الموثوقة هي وسم الاستعلامات (prepared statements)، حيث يتم تمرير كود SQL والبيانات بشكل منفصل. جميع الطرق الأخرى — التهريب، التحقق، WAF — هي طبقات حماية إضافية لكنها لا تحل محل الوسم. وفقاً لـ OWASP (2025)، الوسم يمنع 100% من هجمات SQL Injection.
عند استخدام prepared statements، يتم أولاً تجميع استعلام SQL بواسطة خادم قاعدة البيانات بدون بيانات، ثم يتم تمرير قيم المعاملات بشكل منفصل. تتعامل قاعدة البيانات مع المعاملات كبيانات، وليس ككود قابل للتنفيذ. حتى إذا مرر المهاجم ' OR '1'='1، تفسره قاعدة البيانات كقيمة نصية، وليس ككود SQL. prepared statements مدعومة من جميع اللغات والأطر الحديثة: PDO في PHP، PreparedStatement في Java، cursor.execute في Python.
أطر ORM الحديثة (Hibernate, Entity Framework, SQLAlchemy, Room) تستخدم الوسم تلقائياً عند تنفيذ الاستعلامات، ما لم يتحول المطور إلى الاستعلامات الخام. لكن ORM لا تحمي بالكامل: إنشاءات مثل @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) في JPA تتطلب تمرير المعاملات عبر معاملات مسماة، وليس بالتسلسل. منشئو الاستعلامات (Knex, jOOQ) يوسمون الاستعلامات افتراضياً أيضاً إذا لم تستخدم الطرق الخام.
تهريب الأحرف الخاصة (mysql_real_escape_string) هو طريقة قديمة لا تحمي من جميع أنواع SQL Injection. المشكلة: التهريب يعتمد على الترميز ويمكن تجاوزه عند استخدام الترميزات متعددة البايت (مثلاً، GBK في الأنظمة الآسيوية). استخدم التهريب فقط في الكود القديم حيث يكون الوسم مستحيلاً، ودائماً مع التحقق الصارم لنوع بيانات الإدخال.
| الطريقة | الفعالية | التوصية |
|---|---|---|
| Prepared Statements | 100% | إلزامي لجميع الاستعلامات |
| ORM (استخدام صحيح) | 99% | موصى به |
| تهريب السلاسل النصية | 70% (يعتمد على الترميز) | قديم فقط |
| التحقق من الإدخال (white-list) | 50% (للأرقام فقط) | إضافي |
| WAF | 60% | إضافي |
يجب أن يكون لحساب التطبيق الحد الأدنى من الامتيازات الضرورية: SELECT, INSERT, UPDATE, DELETE — فقط على الجداول التي يحتاجها التطبيق فعلياً. حظر استخدام DROP, TRUNCATE, CREATE لحساب التطبيق. هذا يحد من الضرر حتى في حالة هجوم SQL Injection ناجح: لن يتمكن المهاجم من حذف الجداول أو تنفيذ عمليات إدارية.
يجب أن يكون الاختبار المنتظم لـ SQL Injection جزءاً من خط أنابيب التطوير الآمن. مزيج من التحليل الثابت والمسح الديناميكي واختبار الاختراق اليدوي يعطي أفضل النتائج. وفقاً لـ تقرير الأمن السيبراني لـ Synopsys (2025)، تجد الماسحات الآلية ما يصل إلى 70% من ثغرات SQLi، لكن هجمات Blind المعقدة تتطلب اختباراً يدوياً.
للتطبيقات المحمولة، تحليل SQLite المحلي مهم أيضاً: تحقق من جميع استدعاءات rawQuery، استعلامات ContentProvider واستعلامات Room مع rawQuery. الأدوات: Android Studio Lint (يكشف SQLi في SQLite)، MobSF (Mobile Security Framework) للتحليل الثابت والديناميكي التلقائي لـ APK/IPA. يوصى أيضاً باختبار نقاط نهاية API عبر sqlmap مع اعتراض وسيط لحركة مرور التطبيق المحمول.
الأسئلة الشائعة
SQL Injection يهاجم قواعد البيانات العلائقية عبر استعلامات SQL. NoSQL Injection يؤثر على قواعد البيانات غير العلائقية (MongoDB, Couchbase) عبر عوامل الاستعلام الخاصة بها ($gte, $ne, $where). في MongoDB، الحقن ممكن إذا كان التطبيق يبني مستند BSON من سلسلة JSON. آليات الحماية متشابهة: الوسم والتحقق من الأنواع.
ابحث عن جميع الأماكن التي تُبنى فيها استعلامات SQL عن طريق تسلسل السلاسل النصية مع بيانات المستخدم. ابحث عن أنماط مثل "SELECT ... WHERE id = " + userId أو f"UPDATE ... SET name = '{name}'". كل سطر من هذا القبيل هو حقن SQL محتمل. استبدلها جميعاً باستعلامات موسومة أو prepared statements.
أطر ORM تحمي تلقائياً فقط إذا كنت تستخدم طرق Query Builder والمعاملات المسماة. إذا كنت تستخدم استعلامات خام (nativeQuery في JPA، rawQuery في Room)، فإن الحماية لا تعمل — يجب تمرير المعاملات عبر تعبيرات معدة، وليس عبر تسلسل السلاسل النصية.
Second-Order SQL Injection هو هجوم حيث يتم تخزين البيانات الضارة في قاعدة البيانات كآمنة، ولكنها تُستخدم لاحقاً في استعلام آخر دون تهريب. على سبيل المثال، يسجل مهاجم باسم مستخدم مثل ' OR '1'='1. يتم حفظ البيانات كسلسلة نصية — لا يوجد هجوم عند التسجيل. لكن إذا استخدم استعلام آخر اسم المستخدم في SQL بدون وسم، يتم تفعيل الحقن.
NoSQL Injection قد يكون أكثر خطورة بسبب انخفاض وعي المطورين. المطورون يعرفون SQL Injection ومعظمهم يستخدم ORM، لكن القليل يعرفون NoSQL Injection. في MongoDB، استعلام غير صحيح التكوين قد يعيد جميع مستندات المجموعة. الحماية هي نفسها — prepared statements (وسم BSON) وتحقق صارم من الإدخال.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا