تجاوز المصادقة
admin' OR '1'='1محاولة تجاوز تسجيل الدخول بجعل شرط WHERE صحيحًا دائمًا
لا تُدرج القيم المقدَّمة من المستخدم داخل نص SQL أبدًا — بل تُربط عبر عناصر نائبة للمعاملات (حسب لهجة SQL، مثل $1 أو ?)
يبقى حقن 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 بشكل خاطئ
يمر كل استعلام عبر طبقات متعددة من التحقق الأمني
تُحوَّل كل القيم المقدَّمة من المستخدم إلى معاملات مربوطة. لا تدخل القيم نص SQL كقيم حرفية (literals).
يتم التحقق من كل اسم حقل مقابل البيانات الوصفية لمخطط Prisma قبل توليد الاستعلام.
يتم التحقق من أسماء الجداول والأعمدة والأسماء المستعارة (aliases) واقتباسها/تهريبها بأمان حسب الحاجة.
السماح فقط بعوامل معروفة وآمنة لكل نوع حقل.
ترتيب صارم يضمن أن كل عنصر نائب يقابل معاملًا واحدًا بالضبط حسب ترتيب الإدراج، مما يمنع عدم التطابق.
مخططات براهين لمقاومة الحقن وفق افتراضات التصميم المعلنة (ربط المعاملات، لا تجاوز عبر SQL خام، استخدام صحيح للسائق)
لكل القيم المقدَّمة من المستخدم 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}`
}مجموعة أسماء الحقول 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)
}لأي عامل 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')
}يتم التحقق من جميع معرّفات 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
}لكل موضع عنصر نائب 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 الشائعة وحالات الحواف
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"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
}لا تتجاوز التحقق من المخطط أو فحص الحقول. هذه طبقات أمان حرجة.
تأكد أن مخطط Prisma يعكس بنية قاعدة البيانات بدقة.
يوفر TypeScript أمانًا وقت الترجمة، لكن التحقق وقت التشغيل يضيف دفاعًا في العمق.
سجّل وراقب SQL المُولَّد والمعاملات في الإنتاج لاكتشاف الشذوذ وسوء الاستخدام.
الحماية من حقن SQL مدمجة في كل طبقة من هذه المكتبة. راجع اختبارات الأمان وتفاصيل التنفيذ مباشرةً.