الأمان أولًا

منع حقن SQL

لا تُدرج القيم المقدَّمة من المستخدم داخل نص SQL أبدًا — بل تُربط عبر عناصر نائبة للمعاملات (حسب لهجة SQL، مثل $1 أو ?)

ربط بالمعاملات
100%
كل القيم المقدَّمة من المستخدم تستخدم عناصر نائبة
إدراج القيم في النص
صفر
بيانات المستخدم لا تظهر أبدًا في نص SQL
حفظ الترتيب
مضمون
العناصر النائبة تقابل المعاملات 1:1
اختبارات الأمان
حالية
تغطية محاولات الحقن

فهم حقن SQL

يبقى حقن SQL من أخطر ثغرات تطبيقات الويب

يحدث حقن SQL عندما تُضمَّن بيانات غير موثوقة داخل نص SQL بدون ربط معاملات آمن. يمكن للمهاجمين تغيير معنى الاستعلام للوصول إلى بيانات غير مصرح بها أو تعديل سجلات أو تنفيذ عمليات غير مقصودة.

أنماط هجوم شائعة

تجاوز المصادقة

admin' OR '1'='1

محاولة تجاوز تسجيل الدخول بجعل شرط WHERE صحيحًا دائمًا

استخراج البيانات

' UNION SELECT password FROM users--

استخدام UNION لمحاولة استخراج بيانات من جداول أخرى

أوامر تدميرية

'; DROP TABLE users; --

محاولة إلحاق تعليمات إضافية لحذف البيانات

استغلال خطأ ترتيب المعاملات

استغلال: إذا لم تتطابق العناصر النائبة مع المعاملات، فقد تستخدم فحوص التفويض قيمًا خاطئة

مثال: WHERE userId=$1 AND isAdmin=$2، لكن params=[true, 123] بدلًا من [123, true] قد يمنح صلاحيات admin بشكل خاطئ

استراتيجية حماية متعددة الطبقات

يمر كل استعلام عبر طبقات متعددة من التحقق الأمني

  1. الطبقة 1: ربط تلقائي للمعاملات

    تُحوَّل كل القيم المقدَّمة من المستخدم إلى معاملات مربوطة. لا تدخل القيم نص SQL كقيم حرفية (literals).

    • تُمثَّل قيم المستخدم بعناصر نائبة خاصة بلهجة SQL (مثل $1 أو ?)
    • مُدخلات المصفوفات تُمرَّر كمعاملات (عناصر نائبة موسّعة أو معاملات مصفوفة حسب اللهجة/الاستراتيجية)
    • تُعالَج قيم NULL والقيم الاختيارية دون دمج مُدخلات المستخدم في نص SQL
    • قيم التاريخ/الوقت تُمرَّر كمعاملات
    • قيم JSON تُمرَّر كمعاملات
  2. الطبقة 2: التحقق من أسماء الحقول

    يتم التحقق من كل اسم حقل مقابل البيانات الوصفية لمخطط Prisma قبل توليد الاستعلام.

    • السماح فقط بالحقول المعرَّفة في المخطط
    • التحقق من مرشحات العلاقات عبر البيانات الوصفية للمخطط
    • لا تُقبل أسماء حقول عشوائية
    • رفض الحقول غير المدعومة/المحسوبة عند الاقتضاء
    • رفض حمولات تلويث النموذج الأولي (prototype pollution) (مثل __proto__ وconstructor)
  3. الطبقة 3: تنقية المعرّفات

    يتم التحقق من أسماء الجداول والأعمدة والأسماء المستعارة (aliases) واقتباسها/تهريبها بأمان حسب الحاجة.

    • رفض أحرف التحكم
    • اقتباس الكلمات المحجوزة عند الحاجة
    • دعم تأهيل المخطط حيثما تم إعداده
    • تهريب علامات الاقتباس المزدوجة (أو ما يعادلها في اللهجة) للمعرّفات
    • فرض الحد الأقصى لطول المعرّف حسب اللهجة
  4. الطبقة 4: التحقق من العوامل

    السماح فقط بعوامل معروفة وآمنة لكل نوع حقل.

    • عوامل النصوص: contains, startsWith, endsWith
    • العوامل الرقمية: lt, lte, gt, gte
    • عوامل المصفوفات: in, notIn مع التحقق من النوع
    • العوامل المنطقية: AND, OR, NOT يُتحقق منها بنيويًا
    • رفض العوامل غير المعروفة فورًا
  5. الطبقة 5: ضمان ترتيب المعاملات

    ترتيب صارم يضمن أن كل عنصر نائب يقابل معاملًا واحدًا بالضبط حسب ترتيب الإدراج، مما يمنع عدم التطابق.

    • عداد تسلسلي يمنع إعادة الترتيب
    • بناء SQL ومصفوفة المعاملات بشكل متزامن خطوة بخطوة
    • لا مخازن وسيطة ولا عمليات إعادة ترتيب
    • تحقق الاختبارات: SQL المُولَّد والمعاملات متطابقان دائمًا
    • تطابق واحد لواحد: العنصر النائب N → params[N-1] (أو ما يعادله حسب اللهجة)

ضمانات أمنية شكلية

مخططات براهين لمقاومة الحقن وفق افتراضات التصميم المعلنة (ربط المعاملات، لا تجاوز عبر SQL خام، استخدام صحيح للسائق)

المبرهنة 1: عزل القيم

لكل القيم المقدَّمة من المستخدم V، لا يوجد مسار تنفيذ تظهر فيه V كنص SQL في سلسلة الاستعلام المُولَّدة.

بحكم البناء، تستدعي كل مسارات الكود التي تعالج قيم المستخدم الدالة addParameter(value) التي: 1. تخزّن القيمة في مصفوفة params منفصلة 2. تُرجع رمز عنصر نائب لنص SQL 3. يُلحَق رمز العنصر النائب فقط بسلسلة SQL 4. يستقبل سائق قاعدة البيانات نص SQL وقيم المعاملات بشكل منفصل ∴ لا تُحلَّل قيم المستخدم كصياغة SQL

const addParameter = (params: any[], value: any) => {
  params.push(value)
  const index = params.length
  return `$${index}`
}

المبرهنة 2: انغلاق أسماء الحقول

مجموعة أسماء الحقول F في أي استعلام مُولَّد هي مجموعة جزئية من الحقول المعرَّفة في المخطط S، أي F ⊆ S.

لكل مرجع إلى حقل: 1. يُستخرج اسم الحقل من كائن الاستعلام 2. يُبحث عنه في البيانات الوصفية للمخطط 3. إذا لم يكن الحقل موجودًا في المخطط، يُرمى خطأ 4. لا تصل إلى توليد SQL إلا الحقول التي تم التحقق منها ∴ لا يمكن أن تظهر أسماء حقول عشوائية في SQL

const validateField = (field: string, model: Model) => {
  if (!model.fields.has(field)) {
    throw new Error(`Field ${field} does not exist`)
  }
  return model.fields.get(field)
}

المبرهنة 3: أمان العوامل

لأي عامل O يُطبَّق على الحقل F، ينتمي O إلى مجموعة العوامل المسموح بها لنوع الحقل F وهو T.

خوارزمية التحقق من العوامل: 1. استخراج نوع الحقل T من المخطط 2. تعريف allowed_ops(T) = { العوامل الصالحة للنوع T } 3. لكل عامل O في الاستعلام: - إذا كان O ∉ allowed_ops(T)، يُرمى خطأ - وإلا يُطبَّق العامل باستخدام ربط المعاملات ∴ لا يمكن استخدام إلا العوامل المناسبة للنوع

const ALLOWED_OPS: Record<string, string[]> = {
  String: ['contains', 'startsWith', 'endsWith'],
  Int: ['lt', 'lte', 'gt', 'gte']
}
if (!ALLOWED_OPS[fieldType]?.includes(operator)) {
  throw new Error('Invalid operator')
}

المبرهنة 4: أمان المعرّفات

يتم التحقق من جميع معرّفات SQL (I) لمنع أحرف التحكم ولضمان اقتباس/تهريب آمن حسب اللهجة.

معالجة المعرّفات: 1. الرفض إذا احتوى المعرّف على أحرف تحكم 2. تهريب علامات الاقتباس الداخلية وفق قواعد اللهجة 3. اقتباس المعرّفات عند الحاجة (الكلمات المحجوزة أو الأحرف الخاصة) 4. فرض حدود طول المعرّف لكل لهجة ∴ لا يمكن للمعرّفات كسر صياغة SQL أو إدخال رموز محقونة

const quoteIdentifier = (id: string) => {
  if (/[\x00-\x1F]/.test(id)) {
    throw new Error('Invalid characters')
  }
  const escaped = id.replace(/"/g, '""')
  const needsQuoting = /[^a-z0-9_]/i.test(id) || isReservedKeyword(id)
  return needsQuoting ? `"${escaped}"` : id
}

المبرهنة 5: اتساق ترتيب المعاملات

لكل موضع عنصر نائب N يُضاف إلى نص SQL، يحتوي params[N-1] على القيمة المقصودة لذلك الموضع بالضبط، دون إعادة ترتيب أو عدم تطابق.

ضمان ترتيب المعاملات: 1. متتبّع واحد يحافظ على ترتيب الإدراج 2. كل استدعاء add() يُلحق القيمة بـ params ويُصدر فورًا رمز العنصر النائب التالي 3. يسير بناء نص SQL وبناء params بشكل متزامن خطوة بخطوة 4. لا تحدث عمليات إعادة ترتيب وسيطة ∴ يبقى التطابق واحدًا لواحد طوال العملية

class ParameterTracker {
  private params: any[] = []

  add(value: any): string {
    this.params.push(value)
    return `$${this.params.length}`
  }

  getParams(): any[] {
    return this.params
  }
}

const tracker = new ParameterTracker()
const sql = `WHERE email = ${tracker.add(email)} AND age > ${tracker.add(age)}`
const params = tracker.getParams()

تغطية اختبارات أمان شاملة

تتحقق المجموعة الحالية من الحماية ضد متجهات حقن SQL الشائعة وحالات الحواف

تمرير القيم كمعاملات (basic.test.ts)

  • سلاسل تحتوي علامات اقتباس: user'with'quotes
  • سلاسل تحتوي فواصل منقوطة: user;extra
  • كلمات SQL المفتاحية كقيم: DROP TABLE users
  • حقن مركّب: '; DROP TABLE users; --
  • هجمات UNION: ' UNION SELECT * FROM users--
  • هجمات منطقية: admin' OR '1'='1
  • حقن التعليقات: test@example.com' -- comment

التحقق من أسماء الحقول (identifiers.test.ts)

  • رفض أسماء الحقول الخبيثة في SELECT
  • رفض أسماء الحقول الخبيثة في WHERE
  • رفض أسماء الحقول الخبيثة في ORDER BY
  • رفض حقن SQL في أسماء الحقول
  • رفض الحقول غير الموجودة
  • رفض تلويث النموذج الأولي (prototype pollution): __proto__, constructor

أمان أنماط LIKE (like-patterns.test.ts)

  • حقن أحرف البدل: %' OR '1'='1
  • حقن الشرطة السفلية: test_' OR '1'='1
  • معالجة الشرطة المائلة العكسية: test\\'; DROP--
  • أحرف بدل متعددة: %_%'; DROP--
  • حقن غير حساس لحالة الأحرف: '; UNION SELECT--

أمان عوامل المصفوفات (array-operators.test.ts)

  • IN مع مصفوفات خبيثة: ['; DROP--', 'UNION SELECT--']
  • NOT IN مع حقن: ['; TRUNCATE--', 'DELETE FROM--']
  • معالجة المصفوفة الفارغة
  • التحقق من المصفوفات الكبيرة (100+ عنصر)
  • معالجة المصفوفات مختلطة الأنواع

حالات الحواف (edge-cases.test.ts)

  • حقن Unicode: \u0027 OR \u00271\u0027=\u00271
  • حقن بترميز ست عشري: 0x31=0x31--
  • ترميز URL: %27%3B%20DROP%20TABLE
  • استعلامات مكدّسة: '; DROP TABLE users; SELECT
  • حقن أعمى قائم على الوقت (خاص باللهجة): WAITFOR DELAY '00:00:05'--
  • قائم على UNION: UNION ALL SELECT null, password
  • محاولات حقن من الدرجة الثانية

التحقق من ترتيب المعاملات (basic.test.ts)

  • مواضع عناصر نائبة متتابعة
  • مصفوفة المعاملات تطابق ترتيب العناصر النائبة
  • الاستعلامات المعقدة تحافظ على الترتيب عبر الشروط
  • شروط OR/AND المتداخلة تحافظ على تسلسل المعاملات
  • مرشحات العلاقات تحافظ على الربط الصحيح للمعاملات

آمن مقابل غير آمن: مقارنة الكود

❌ غير آمن: دمج السلاسل النصية
unsafe.ts
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"
✅ آمن: ربط تلقائي للمعاملات
safe.ts
const email = req.body.email
const { sql, params } = toSQL('User', 'findMany', {
  where: { email }
})

const result = { sql, params }

تفاصيل التنفيذ

إدارة المعاملات

تتبّع مركزي للمعاملات يضمن أن كل قيمة مقدَّمة من المستخدم مربوطة كمعامل وثابتة الترتيب

class ParameterTracker {
  private params: any[] = []

  add(value: any): string {
    this.params.push(value)
    return `$${this.params.length}`
  }

  getParams(): any[] {
    return this.params
  }
}

التحقق من الحقول

التحقق المعتمد على المخطط يمنع الوصول العشوائي إلى الحقول

function validateField(
  fieldName: string,
  model: ModelInfo
): FieldInfo {
  const field = model.fields.get(fieldName)
  if (!field) {
    throw new Error(
      `Field "${fieldName}" does not exist`
    )
  }
  return field
}

اقتباس المعرّفات

التحقق من المعرّفات واقتباسها وفق اللهجة يمنع حقن الصياغة عبر المعرّفات

function quoteIdentifier(identifier: string): string {
  if (/[\x00-\x1F]/.test(identifier)) {
    throw new Error('Invalid control characters')
  }

  const escaped = identifier.replace(/"/g, '""')
  const needsQuoting = /[^a-z0-9_]/i.test(identifier) || isReservedKeyword(identifier)

  if (needsQuoting) {
    return `"${escaped}"`
  }

  return identifier
}

أفضل ممارسات الأمان

لا تُعطّل التحقق أبدًا

لا تتجاوز التحقق من المخطط أو فحص الحقول. هذه طبقات أمان حرجة.

افعل
استخدم المكتبة كما صُممت مع التحقق الكامل
لا تفعل
تجاوز التحقق من المخطط أو حقن SQL خام في نص الاستعلام

حافظ على تحديث المخطط

تأكد أن مخطط Prisma يعكس بنية قاعدة البيانات بدقة.

افعل
شغّل prisma db pull وprisma generate بعد تغييرات المخطط
لا تفعل
استخدام DMMF أو تعريفات مخطط قديمة

تحقق من أنواع المُدخلات

يوفر TypeScript أمانًا وقت الترجمة، لكن التحقق وقت التشغيل يضيف دفاعًا في العمق.

افعل
استخدم أنواع Prisma المولّدة لوسائط الاستعلام
لا تفعل
تحويل مُدخلات المستخدم إلى any قبل تمريرها للاستعلامات

راقب أنماط الاستعلامات

سجّل وراقب SQL المُولَّد والمعاملات في الإنتاج لاكتشاف الشذوذ وسوء الاستخدام.

افعل
فعّل تسجيل الاستعلامات وراجع الأنماط
لا تفعل
التشغيل في الإنتاج دون مراقبة

الأمان عبر التصميم

الحماية من حقن SQL مدمجة في كل طبقة من هذه المكتبة. راجع اختبارات الأمان وتفاصيل التنفيذ مباشرةً.