حقن SQL وحقن NoSQL
حقن SQL وحقن NoSQL
حقن SQL (SQLi) هو أحد أقدم وأخطر ثغرات تطبيقات الويب. على الرغم من فهمه جيدًا، إلا أنه لا يزال منتشرًا، ويحتل المرتبة الثالثة في OWASP Top 10 تحت "الحقن". تواجه قواعد بيانات NoSQL، على الرغم من اختلافها في البنية، ثغرات حقن مماثلة. يغطي هذا الدرس هجمات حقن SQL وNoSQL ومنعها.
فهم حقن SQL
يحدث حقن SQL عندما يتم إرسال بيانات غير موثوقة إلى مترجم كجزء من أمر أو استعلام. تخدع بيانات المهاجم العدائية المترجم في تنفيذ أوامر غير مقصودة أو الوصول إلى بيانات غير مصرح بها.
<?php
// خطير: تسلسل مباشر لإدخال المستخدم
$username = $_POST['username'];
$password = $_POST['password'];
$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = mysqli_query($conn, $query);
?>
حمولة الهجوم:
اسم المستخدم: admin'--
كلمة المرور: anything
الاستعلام الناتج:
SELECT * FROM users WHERE username = 'admin'--' AND password = 'anything'
يعلق -- على فحص كلمة المرور، مما يسمح بتسجيل الدخول دون كلمة مرور!
أنواع حقن SQL
1. حقن SQL الكلاسيكي
معالجة مباشرة لاستعلامات SQL من خلال إدخال المستخدم لاستخراج أو تعديل البيانات:
اسم المستخدم: ' OR '1'='1
كلمة المرور: ' OR '1'='1
الاستعلام: SELECT * FROM users WHERE username='' OR '1'='1' AND password='' OR '1'='1'
النتيجة: دائمًا صحيح، يعيد جميع المستخدمين
// مثال 2: استخراج البيانات
معرف المنتج: 1' UNION SELECT username, password, NULL FROM users--
الاستعلام: SELECT name, price, description FROM products WHERE id='1' UNION SELECT username, password, NULL FROM users--'
النتيجة: يستخرج جميع أسماء المستخدمين وكلمات المرور جنبًا إلى جنب مع بيانات المنتج
2. حقن SQL الأعمى
عندما لا يعرض التطبيق أخطاء قاعدة البيانات أو نتائج الاستعلام، يستخدم المهاجمون تقنيات قائمة على القيم المنطقية أو قائمة على الوقت:
// اختبار ما إذا كان الحرف الأول من كلمة مرور المسؤول هو 'a'
اسم المستخدم: admin' AND SUBSTRING(password,1,1)='a'--
إذا تصرفت الصفحة بشكل طبيعي ← الحرف الأول هو 'a'
إذا تصرفت الصفحة بشكل مختلف ← الحرف الأول ليس 'a'
// حقن SQLi أعمى قائم على الوقت
معرف المنتج: 1' AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)--
إذا تأخرت الاستجابة 5 ثوانٍ ← الحرف الأول هو 'a'
إذا كانت الاستجابة فورية ← الحرف الأول ليس 'a'
// استخراج آلي (كود وهمي)
password = ""
for position in 1 to 32:
for char in 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789':
if test_char(position, char):
password += char
break
3. حقن SQL من الدرجة الثانية
يتم تخزين البيانات الضارة بأمان ولكن يتم استخدامها لاحقًا بشكل غير آمن في جزء مختلف من التطبيق:
// مخزن بأمان باستخدام استعلام معلمي
$stmt = $pdo->prepare("INSERT INTO users (username, email) VALUES (?, ?)");
$stmt->execute(['admin'--', 'attacker@evil.com']);
الخطوة 2: تحديث الملف الشخصي يستخدم اسم المستخدم المخزن بشكل غير آمن
$username = $row['username']; // يسترجع: admin'--
$query = "UPDATE users SET bio='$bio' WHERE username='$username'";
// يصبح الاستعلام: UPDATE users SET bio='...' WHERE username='admin'--'
// يعلق على بقية الاستعلام، مما قد يؤثر على مستخدمين خاطئين
منع حقن SQL
1. الاستعلامات المعلمية (البيانات المحضرة)
الدفاع الأكثر فعالية ضد حقن SQL هو استخدام الاستعلامات المعلمية، حيث يتم الاحتفاظ بكود SQL والبيانات منفصلين:
<?php
$pdo = new PDO('mysql:host=localhost;dbname=mydb', $user, $pass);
// بيان محضر مع عناصر نائبة مسماة
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND password = :password");
$stmt->execute([
':username' => $_POST['username'],
':password' => hash('sha256', $_POST['password'])
]);
$user = $stmt->fetch();
// بيان محضر مع عناصر نائبة موضعية
$stmt = $pdo->prepare("INSERT INTO products (name, price) VALUES (?, ?)");
$stmt->execute([$name, $price]);
?>
// Node.js مع MySQL
const mysql = require('mysql2/promise');
const [rows] = await connection.execute(
'SELECT * FROM users WHERE username = ? AND email = ?',
[username, email]
);
2. استخدام ORM وواجهات برمجة التطبيقات الآمنة
توفر مخططات الكائنات العلائقية طبقة حماية إضافية عند استخدامها بشكل صحيح:
$users = User::where('username', $request->input('username'))
->where('active', true)
->get();
// آمن: منشئ الاستعلام مع الربط
$users = DB::table('users')
->where('votes', '>', 100)
->get();
// خطير: استعلامات خام بدون ربط
$users = DB::select("SELECT * FROM users WHERE name = '" . $name . "'"); // معرض للخطر!
// آمن: استعلامات خام مع ربط
$users = DB::select("SELECT * FROM users WHERE name = ?", [$name]);
// آمن: ربط مسمى
$users = DB::select("SELECT * FROM users WHERE name = :name", ['name' => $name]);
3. التحقق من صحة المدخلات وتعقيمها
على الرغم من أنه غير كافٍ وحده، إلا أن التحقق من صحة المدخلات يضيف الدفاع في العمق:
// التحقق من القائمة البيضاء للأنواع المتوقعة
function validateProductId($id) {
if (!is_numeric($id) || $id < 1) {
throw new InvalidArgumentException("معرف منتج غير صالح");
}
return (int) $id;
}
// القائمة البيضاء لأسماء الأعمدة (لا يمكن استخدام المعلمات لهذه)
$allowedSortColumns = ['name', 'price', 'created_at'];
$sortColumn = $_GET['sort'] ?? 'name';
if (!in_array($sortColumn, $allowedSortColumns)) {
$sortColumn = 'name';
}
$stmt = $pdo->prepare("SELECT * FROM products ORDER BY $sortColumn DESC");
// الحد من طول الإدخال
$username = substr($_POST['username'], 0, 50);
// رفض الأنماط الخطرة (الملاذ الأخير، يسهل تجاوزه)
$blacklist = ['union', 'select', 'drop', 'insert', 'update', 'delete', '--', '/*'];
foreach ($blacklist as $keyword) {
if (stripos($input, $keyword) !== false) {
die("تم اكتشاف إدخال مشبوه");
}
}
?>
4. الوصول إلى قاعدة البيانات بأقل امتياز
الحد من الضرر الناتج عن الحقن الناجح من خلال تقييد أذونات مستخدم قاعدة البيانات:
CREATE USER 'webapp_user'@'localhost' IDENTIFIED BY 'strong_password';
-- منح الأذونات الضرورية فقط
GRANT SELECT, INSERT, UPDATE ON myapp.products TO 'webapp_user'@'localhost';
GRANT SELECT, INSERT ON myapp.orders TO 'webapp_user'@'localhost';
GRANT SELECT ON myapp.users TO 'webapp_user'@'localhost';
-- لا تمنح:
-- DROP, CREATE, ALTER (تعديل الهيكل)
-- FILE (قراءة/كتابة ملفات الخادم)
-- SUPER, PROCESS (إدارة الخادم)
-- الوصول إلى mysql.user أو جداول النظام الأخرى
-- مستخدمون منفصلون لوظائف مختلفة
CREATE USER 'webapp_readonly'@'localhost' IDENTIFIED BY 'password';
GRANT SELECT ON myapp.* TO 'webapp_readonly'@'localhost';
حقن NoSQL
تستخدم قواعد بيانات NoSQL مثل MongoDB وCouchDB وCassandra لغات استعلام مختلفة، لكنها ليست محصنة ضد هجمات الحقن:
أمثلة على حقن MongoDB
app.post('/login', async (req, res) => {
const user = await db.collection('users').findOne({
username: req.body.username,
password: req.body.password
});
});
// حمولة الهجوم (ترسل كـ JSON)
{
"username": {"$ne": null},
"password": {"$ne": null}
}
// الاستعلام الناتج
db.users.findOne({
username: {$ne: null}, // غير مساوٍ لـ null (دائمًا صحيح)
password: {$ne: null} // غير مساوٍ لـ null (دائمًا صحيح)
})
// يعيد أول مستخدم في قاعدة البيانات، متجاوزًا المصادقة!
// هجوم آخر: $gt (أكبر من)
{
"username": "admin",
"password": {"$gt": ""}
}
// يطابق أي كلمة مرور، حيث أن جميع السلاسل أكبر من السلسلة الفارغة
منع حقن NoSQL
app.post('/login', async (req, res) => {
// تأكد من أن المدخلات سلاسل، وليست كائنات
const username = String(req.body.username);
const password = String(req.body.password);
const user = await db.collection('users').findOne({
username: username,
password: password
});
});
// 2. التحقق من صحة المدخلات مع المخطط
const Joi = require('joi');
const loginSchema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().min(8).required()
});
const { error, value } = loginSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
// 3. استخدم ORM/ODM مع حماية مدمجة
// Mongoose لـ MongoDB
const UserSchema = new mongoose.Schema({
username: { type: String, required: true },
password: { type: String, required: true }
});
const User = mongoose.model('User', UserSchema);
// استعلام آمن باستخدام Mongoose
const user = await User.findOne({ username: username });
// 4. تعطيل تحليل استعلام المشغل في Express
// باستخدام express-mongo-sanitize
const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize());
// يزيل هذا تلقائيًا أي مفاتيح تبدأ بـ $ أو تحتوي على .
أنواع أخرى من حقن NoSQL
// معرض للخطر:
db.view('users', 'by_username', {
key: req.query.username // يمكن أن يكون كائنًا مع startkey/endkey
});
// حقن Redis عبر برامج Lua النصية
// معرض للخطر:
const script = `return redis.call('GET', '${req.body.key}')`;
redis.eval(script);
// الهجوم: key = ") redis.call('FLUSHALL') --"
// حقن Cassandra CQL
// معرض للخطر:
const query = `SELECT * FROM users WHERE username='${username}'`;
// آمن: استخدم الاستعلامات المعلمية
const query = 'SELECT * FROM users WHERE username=?';
client.execute(query, [username]);
تقنيات الحماية المتقدمة
1. الإجراءات المخزنة (SQL)
DELIMITER //
CREATE PROCEDURE GetUserByUsername(IN p_username VARCHAR(50))
BEGIN
SELECT user_id, username, email FROM users WHERE username = p_username;
END //
DELIMITER ;
// PHP استدعاء إجراء مخزن
<?php
$stmt = $pdo->prepare("CALL GetUserByUsername(?)");
$stmt->execute([$username]);
$user = $stmt->fetch();
?>
2. جدران الحماية لتطبيقات الويب (WAF)
يمكن لـ WAFs اكتشاف وحظر محاولات حقن SQL، ولكن لا ينبغي أن تكون الدفاع الوحيد:
- كلمات SQL متعددة (SELECT، UNION، FROM)
- تعليقات SQL (--, /* */)
- عوامل تشغيل منطقية في سياقات غير متوقعة
- هجمات UNION
- أنماط الهجوم القائمة على الوقت (SLEEP، BENCHMARK)
القيود:
- يمكن تجاوزها بالترميز (ترميز URL، Unicode، hex)
- يمكن أن تحظر الإيجابيات الكاذبة حركة المرور الشرعية
- تقنيات التجاوز في اليوم صفر
- عبء الأداء
3. مراقبة نشاط قاعدة البيانات
- أنماط الاستعلام غير العادية (UNION، JOINs مفرطة)
- الاستعلامات إلى جداول النظام (information_schema، mysql.user)
- محاولات تسجيل دخول فاشلة متعددة
- الاستعلامات مع وظائف sleep/delay
- الوصول إلى الإجراءات الإدارية
- تسريب البيانات (مجموعات نتائج كبيرة)
// مثال: تسجيل الاستعلامات المشبوهة
<?php
$logFile = '/var/log/app/suspicious_queries.log';
if (preg_match('/union|information_schema|benchmark|sleep/i', $query)) {
error_log(date('Y-m-d H:i:s') . " - استعلام مشبوه: " . $query . "\n", 3, $logFile);
// أيضًا تنبيه فريق الأمان
}
?>
اختبار ثغرات الحقن
1. علامة اقتباس مفردة: ' (يجب أن يتسبب في خطأ إذا كان معرضًا للخطر)
2. اختبار منطقي: ' OR '1'='1 و ' OR '1'='2
3. قائم على الوقت: ' AND SLEEP(5)--
4. اختبار UNION: ' UNION SELECT NULL--
5. حقن التعليق: --+, #, /*
// الأدوات الآلية
- SQLMap (أداة شاملة لحقن SQL)
- Burp Suite (مع ماسح SQLi)
- NoSQLMap (لقواعد بيانات NoSQL)
- Havij، jSQL Injection
// مثال على SQLMap
sqlmap -u "http://example.com/product?id=1" --batch --dbs
sqlmap -u "http://example.com/product?id=1" -D mydb --tables
sqlmap -u "http://example.com/product?id=1" -D mydb -T users --dump
1. أنشئ صفحة تسجيل دخول PHP/MySQL أساسية مع ثغرة حقن SQL متعمدة
2. اختبرها مع 5 حمولات حقن مختلفة على الأقل
3. أعد كتابتها باستخدام البيانات المحضرة
4. أضف طبقة التحقق من صحة المدخلات
5. نفذ تقييد المعدل لمنع القوة الغاشمة
6. أنشئ إصدار MongoDB وقم بتأمينه ضد حقن NoSQL
7. وثق الاختلافات بين منع حقن SQL وNoSQL
مكافأة: أعد إعداد SQLMap وقم بتشغيله ضد إصدارك المعرض للخطر، ثم تحقق من فشله ضد الإصدار المؤمن.
التأثير في العالم الحقيقي
- 2020 - حقن SQL في Freepik: كشف 8.3 مليون سجل مستخدم بما في ذلك رسائل البريد الإلكتروني وكلمات المرور المجزأة
- 2019 - حقن SQL في GoDaddy: اختراق 28,000 حساب عميل من خلال ثغرة لوحة تحكم الاستضافة
- 2017 - خرق Equifax: أدى حقن SQL في إطار عمل تطبيق الويب إلى سرقة 147 مليون سجل
- 2014 - Yahoo Voices: كشف حقن SQL 450,000 كلمة مرور نصية عادية
الخلاصة
يظل حقن SQL وNoSQL تهديدات حرجة على الرغم من كونها ثغرات مفهومة جيدًا. الدفاع الأساسي هو دائمًا استخدام الاستعلامات المعلمية أو البيانات المحضرة التي تفصل الكود عن البيانات. توفر ORMs حماية جيدة عند استخدامها بشكل صحيح. يضيف التحقق من صحة المدخلات والوصول إلى قاعدة البيانات بأقل امتياز والمراقبة طبقات أمان إضافية. تتطلب قواعد بيانات NoSQL التحقق من صحة النوع وتعقيم المشغل. يساعد الاختبار الأمني المنتظم ومراجعة الكود في ضمان اكتشاف ثغرات الحقن قبل النشر في الإنتاج.