ما هو حقن SQL
حقن SQL (SQLi) ثغرة يُلصق فيها نص يقدّمه المستخدم داخل استعلام قاعدة بيانات فتُشغّله قاعدة البيانات في النهاية كـSQL. كان التطبيق يقصد إرسال قيمة، كعنوان بريد إلكتروني أو معرّف منتج؛ فيرسل المهاجم مقطعًا من لغة الاستعلام بدلًا منها، ويتغيّر معنى الاستعلام.
السبب الجذري دائمًا نفسه: كود يبني استعلامًا بـتجميع النصوص (string concatenation)، يمزج نوعين من البيانات في نص واحد. نص الاستعلام تعليمات كتبتها أنت. والقيمة بيانات كتبها شخص آخر. وحالما يُلصقان معًا، لا تملك قاعدة البيانات طريقة لتعرف أين تتوقف تعليماتك وأين يبدأ إدخال الزائر.
لهذا لا يخص حقن SQL قاعدة MySQL أو PostgreSQL أو SQL Server أو SQLite دون غيرها، ولا يخص PHP كذلك. أي لغة، وأي مُشغّل اتصال، وأي قاعدة بيانات عرضة له لحظة تجميع استعلام من نصوص غير موثوقة. مصنَّف تحت CWE-89، وظهر الحقن في كل إصدار من OWASP Top 10. مجموعة البحث حوله، "هجوم SQL"، "ثغرة SQL"، "SQL attack"، كلها تصف هذا الخطأ الواحد.
الخبر الجيد أن الحل موحّد بنفس القدر، وعمره عقود، ومبني في كل مُشغّل قاعدة بيانات مستخدَم اليوم: أرسل الاستعلام والقيم منفصلين.
كيف يعمل: المثال الكلاسيكي
تخيّل فحص تسجيل دخول مكتوب كما كتبته كتيبات تعليمية لا تُحصى في الماضي. يأتي اسم المستخدم وكلمة المرور من نموذج، ويُدرجان مباشرة داخل نص SQL.
مع إدخال طبيعي يفعل الاستعلام ما ينبغي. تخيّل الآن زائرًا يكتب ' OR '1'='1 في حقل كلمة المرور. تُغلق علامة التنصيص الحرفية التي فتحها المطوّر، ويصبح الباقي جزءًا من جملة WHERE. ولأن '1'='1' صحيحة دائمًا ويرتبط AND بقوة أكبر من OR، يطابق الشرط كل صف، ويسجّل الكود دخول الزائر كأول مستخدم في الجدول، وهو غالبًا المسؤول.
تلك العلامة الواحدة هي الفكرة كاملة. كل ما تبقى في هذا الدليل، أنواع الهجوم المختلفة، والأثر، والدفاعات، ينتج عن أن قاعدة البيانات استلمت نصًّا واحدًا وفسّرت نص المهاجم كأنه كود.
(تخزين كلمات المرور كنص صريح ومقارنتها في SQL خطأ ثانٍ في هذا المثال. الكود الحقيقي يجلب المستخدم بالاسم ويفحص كلمة المرور بـpassword_verify() مقابل تجزئة (hash). احتُفظ به هنا لأنه أقصر طريقة لإظهار الحقن.)
عرضة للاختراق: إدخال المستخدم مُجمَّع داخل الاستعلام (PHP)
<?php
// DO NOT DO THIS
$user = $_POST['username'];
$pass = $_POST['password'];
$sql = "SELECT id FROM users
WHERE username = '$user' AND password = '$pass'";
$row = $pdo->query($sql)->fetch();
// With password ' OR '1'='1 the database receives:
// SELECT id FROM users
// WHERE username = 'admin' AND password = '' OR '1'='1'
// ...which is true for every row.
الحل: الاستعلامات المُعامَلة، في PHP وPython
الاستعلام المُعامَل (يُسمّى أيضًا prepared statement أو bound parameters) يرسل نص SQL بعناصر نائبة، ? أو :name أو %s بحسب المُشغّل، ويرسل القيم منفصلة. تحلّل قاعدة البيانات الاستعلام وتخطط له أولًا، ثم تُدخل القيم كبيانات. علامة تنصيص داخل قيمة ما هي إلا حرف في نص؛ لا تستطيع أبدًا إغلاق حرف أو إضافة جملة، لأن التحليل انتهى أصلًا.
هذه ليست خطوة تنظيف قد تفوّت حالة. إنها تُزيل الآلية كاملة، ولهذا تتصدّر كل قائمة وقاية.
في PHP، استخدم PDO أو mysqli بالعناصر النائبة. مع PDO، اضبط ترميز المحارف في DSN وأوقف المحاكاة المُعدَّة مسبقًا (emulated prepares) كي يرسل المُشغّل جملًا مُعدّة فعلية من جهة الخادم، وفعّل الاستثناءات كي لا تُتجاهَل الأعطال بصمت. في Python، يأخذ كل مُشغّل DB-API (sqlite3، psycopg، mysqlclient، PyMySQL) القيم كوسيط منفصل لـexecute(). الفخ في Python هو تنسيق النص بنفسك بـf-string أو % قبل استدعاء execute(): تبدو النتيجة مُعامَلة لكنها تجميع.
تنطبق القاعدة نفسها في كل بيئة أخرى: PreparedStatement في Java، وSqlParameter في .NET، وعناصر نائبة $1 في node-postgres، و? في database/sql في Go.
آمن: عناصر نائبة، القيم تُرسل منفصلة (PHP PDO وPython)
<?php
$pdo = new PDO('mysql:host=db;dbname=shop;charset=utf8mb4', $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
$stmt = $pdo->prepare('SELECT id, password_hash FROM users WHERE username = ?');
$stmt->execute([$_POST['username']]);
$row = $stmt->fetch();
$ok = $row && password_verify($_POST['password'], $row['password_hash']);
# Python (psycopg 3, PostgreSQL)
cur.execute(
"SELECT id, password_hash FROM users WHERE username = %s",
(username,), # values go here, never into the string
)
# WRONG, still injectable: the string is built before execute() sees it
cur.execute(f"SELECT id FROM users WHERE username = '{username}'")
أنواع حقن SQL
تصنّف مقالات الأمان حقن SQL بحسب كيف يستعيد المهاجم البيانات. الثغرة نفسها في كل الحالات؛ ما يختلف هو ما يكشفه التطبيق.
ضمن القناة، القائم على UNION. تعود نتيجة الاستعلام المحقون داخل الصفحة نفسها. بإلحاق UNION SELECT، يضيف المهاجم صفوفًا من جدول آخر، كجدول المستخدمين، إلى قائمة المنتجات التي كانت الصفحة تعرضها أصلًا. هذا أسرع نوع للاستغلال، وأسهله للرصد في الاختبار.
ضمن القناة، القائم على الخطأ. يطبع التطبيق رسائل خطأ قاعدة البيانات. يستفز المهاجم أخطاءً يحتوي نصّها على البيانات التي يريدها، كاسم جدول أو قيمة. إظهار أخطاء SQL الخام للزوار يحوّل ثغرة عمياء إلى ثغرة مقروءة، وهذا سبب جيد لتسجيل الأخطاء من جهة الخادم وإظهار صفحة عامة للمستخدمين.
عمياء، قائمة على منطق صحيح/خطأ (boolean). لا تُظهر الصفحة بيانات ولا أخطاء، لكنها تتصرف بشكل مختلف حين يكون شرط صحيحًا أو خطأ: منتج يظهر أو لا يظهر، صفحة 200 أو 404. بسؤال أسئلة نعم/لا واحدًا تلو الآخر، يقرأ المهاجم البيانات قطعة بعد قطعة. بطيء، لكن الأدوات تؤتمته.
عمياء، قائمة على الوقت. حتى محتوى الصفحة متطابق، فيجعل المهاجم قاعدة البيانات تنتظر حين يكون شرط صحيحًا ويقيس زمن الاستجابة. إن بدت كل الاستجابات متماثلة، يبقى التوقيت قناة.
خارج القناة. يجعل المهاجم خادم قاعدة البيانات نفسه يتصل بنظام يتحكم به، عادةً عبر استجواب DNS أو طلب HTTP تُحرّكه دالة قاعدة بيانات. يعتمد على ميزات قاعدة البيانات وتوافر خروج شبكي، وهذا سبب آخر لئلا يستطيع خادم قاعدة البيانات فتح اتصالات صادرة لا حاجة له بها.
من الدرجة الثانية (مخزّنة). يُخزَّن الإدخال بأمان في المرة الأولى، مُعامَلًا بشكل صحيح، ثم يُقرأ لاحقًا ويُجمَّع في استعلام مختلف، غالبًا في تقرير إداري أو مهمة خلفية. يثق المطورون بالبيانات التي أتت من قاعدتهم هم؛ تلك الثقة هي الخطأ. الدفاع هو نفسه في كل مكان: عامِل كل استعلام، بما فيها تلك المبنية من قيم خزّنتها أنت بنفسك.
ما يحصل عليه المهاجم فعليًا
يُحدَّد الأثر بما يُسمح لحساب قاعدة البيانات الذي يستخدمه التطبيق أن يفعله، ولهذا يهم أقل الصلاحيات كثيرًا بعد قليل في هذا الدليل.
قراءة البيانات. النتيجة الشائعة: سجلات العملاء، عناوين البريد الإلكتروني، تجزئات كلمات المرور، الطلبات، مفاتيح API المخزَّنة في جداول الإعدادات. أي جدول يستطيع ذلك الحساب تنفيذ SELECT عليه قابل للقراءة، لا الجدول الذي يلمسه الاستعلام الضعيف فقط.
تجاوز المصادقة. كما في مثال تسجيل الدخول، يتحول شرط ينبغي أن يكون محددًا إلى شرط صحيح دائمًا.
تغيير البيانات أو تدميرها. إن استطاع الحساب تنفيذ UPDATE أو INSERT أو DELETE، فكذلك المهاجم: تغيير الأسعار، إنشاء مستخدمين مسؤولين، مسح جداول. تتيح بعض توافيق المُشغّل وقاعدة البيانات عدة جمل في استدعاء واحد، وهذا يوسّع الأثر أكثر.
الوصول إلى الخادم. بصلاحيات قوية، تستطيع بعض قواعد البيانات قراءة أو كتابة ملفات على المضيف أو تشغيل أوامر نظام التشغيل. على حساب قاعدة بيانات بصلاحيات إدارية، قد يتحول حقن SQL إلى اختراق خادم كامل.
هذا ليس نظريًا. كان حقن SQL نقطة الدخول في اختراق Heartland Payment Systems عام 2008، حيث سُرقت بيانات ما يتجاوز 100 مليون بطاقة بكثير؛ وفي اختراق TalkTalk عام 2015 في المملكة المتحدة، الذي أسفر عن غرامة تنظيمية؛ وفي الاستغلال الجماعي لـMOVEit Transfer عام 2023 (CVE-2023-34362)، حيث استُخدمت ثغرة حقن واحدة في منتج نقل ملفات ضد آلاف المؤسسات. ثغرة قديمة، وعناوين أخبار حالية.
الوقاية، بحسب الأهمية
1. استعلامات مُعامَلة في كل مكان. كل استعلام، كل قيمة، بما فيها القيم من قاعدة بياناتك نفسها، وملفات تعريف الارتباط، والترويسات، والخدمات الداخلية. هذا وحده يُغلق الثغرة. الخطوات الأخرى تحدّ من الضرر عندما ينسى أحدهم، في مكان ما.
2. استخدم ORM أو بانيَ الاستعلامات بشكل صحيح، واعرف مخارجه الخام. تُعامِل Eloquent وDoctrine وDjango ORM وSQLAlchemy وHibernate وEntity Framework الاستعلامات العادية نيابةً عنك. لكنها لا تحمي المقاطع الخام: whereRaw وDB::raw وselectRaw وorderByRaw في Laravel، و.extra() و.raw() في Django، وtext() في SQLAlchemy، وFromSqlRaw في EF Core. كلها تقبل روابط (bindings)؛ استخدمها. البحث عن أسماء تلك الطرق أسرع مراجعة كود تستطيع فعلها.
3. ضع قائمة سماح لما لا يمكن أن يكون معاملًا. تحمل العناصر النائبة قيمًا، لا مُعرّفات. اسم جدول، أو عمود في ORDER BY، أو كلمتا ASC/DESC لا يمكن ربطها، فمعامل "الترتيب بحسب" يجب أن يُربط بقائمة ثابتة من أسماء الأعمدة في الكود. لا تمرّره أبدًا كما هو.
4. أقل الصلاحيات لمستخدم قاعدة البيانات. يجب أن يتصل تطبيق الويب كمستخدم يستطيع SELECT وINSERT وUPDATE وDELETE على مخططه (schema) الخاص ولا شيء آخر: بلا DROP، وبلا GRANT، وبلا وصول إلى الملفات، وبلا قواعد بيانات أخرى، وأبدًا لا حساب root أو sa. تُشغَّل عمليات الترحيل (migrations) بمستخدم منفصل أقوى. إن تسرّب حقن، هذا ما يقرر هل يسرّب مخططًا واحدًا أو يملك الخادم كله.
5. التحقق من الإدخال كدفاع بالعمق. معرّف الطلب يجب أن يكون عددًا صحيحًا، والتاريخ يجب أن يُحلَّل كتاريخ، ورمز البلد حرفان. التحقق من الأنواع والصيغ عند حدود تطبيقك يرفض كثيرًا من الإدخال الخبيث مبكرًا ويرصد الأخطاء. ليس الحل: حقل الاسم يجب أن يقبل O'Brien، وحقل التعليق يقبل تقريبًا أي شيء.
6. لا تُسرِّب الأخطاء. سجّل أخطاء قاعدة البيانات من جهة الخادم مع الاستعلام والسياق؛ وأظهر للزائر صفحة خطأ عامة. في PHP يعني ذلك display_errors=Off في الإنتاج.
مقاطع خام بروابط، وترتيب بقائمة سماح، ومستخدم بأقل الصلاحيات
// Laravel: raw expressions must carry their own bindings
$orders = DB::table('orders')
->whereRaw('total > ? AND status = ?', [$min, $status])
->get();
// ORDER BY cannot be bound: map input to known columns
$sortable = ['created_at', 'total', 'status'];
$column = in_array($request->sort, $sortable, true) ? $request->sort : 'created_at';
$dir = $request->dir === 'asc' ? 'asc' : 'desc';
$orders = Order::orderBy($column, $dir)->paginate(50);
-- MySQL: the app user gets data access to its own schema only
CREATE USER 'shop_app'@'10.0.%' IDENTIFIED BY '...';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'10.0.%';
لماذا التهريب (escaping) غير كافٍ
قبل أن تصبح الجمل المُعدّة مسبقًا شائعة، كانت النصيحة تهريب علامات التنصيص: addslashes()، ثم mysql_real_escape_string()، ثم mysqli::real_escape_string(). التهريب لا يزال في كثير من الكود، ويفشل بطرق متوقعة.
يحمي السياقات المقتبَسة فقط. لا توجد علامات تنصيص حول القيمة في WHERE id = $id، فلا شيء لتهريبه، ويعبر إدخال مثل 1 OR 1=1 دون لمس. ينطبق الأمر نفسه على LIMIT وORDER BY وأي شيء رقمي.
يعتمد على ترميز المحارف. يجب أن تتوافق دوال التهريب مع ترميز الاتصال. سمحت عدم التطابقات بين ترميز العميل والخادم لتسلسلات متعددة البايت بابتلاع الشرطة المائلة العكسية الخاصة بالتهريب؛ ولم يعرف addslashes() الترميز أصلًا.
يجب تذكره في كل مرة بلا استثناء. استدعاء واحد منسي في استعلام واحد، أو قيمة مُهرَّبة لـHTML بدل SQL، والثغرة تعود. تجعل الاستعلامات المُعامَلة المسار الآمن هو المسار الافتراضي.
ينطبق المنطق نفسه على القوائم السوداء ودوال "التنقية" التي تحذف كلمات مثل SELECT أو --. أنفق المهاجمون عشرين عامًا في إيجاد ترميزات وتعليقات واختلافات حالة أحرف تتسلل من تلك المرشِّحات. عامِل أي مرشِّح مصنوع منزليًا كراحة، لا كحماية أبدًا.
اختبار تطبيقك بأمان
ابدأ بالكود، لا بالهجوم. معظم حقن SQL مرئي في بحث عن الكود: استعلامات مُجمَّعة بـ. أو + أو إدراج نص (interpolation) أو sprintf، وطرق ORM الخام مستدعاة بمتغيّر. تملك أدوات التحليل الساكن مثل Semgrep وCodeQL قواعد لهذا النمط بالضبط، وتستطيع العمل في CI عند كل طلب سحب (pull request).
ثم اكتب اختبارات تمرر قيمًا تبدو عدائية لكنها غير مؤذية، علامة تنصيص واحدة، اسم مثل O'Brien، نص طويل جدًا، عبر نماذجك وواجهاتك البرمجية، وتأكّد أن التطبيق يعيد نتيجة طبيعية أو خطأ تحقق، لا 500 أبدًا ولا رسالة خطأ قاعدة بيانات أبدًا. تبقى هذه الاختبارات في الحزمة وتحمي من الانتكاسات.
للاختبار الديناميكي، تستجوب فاحصات مفتوحة المصدر مثل OWASP ZAP وsqlmap التطبيقات العاملة بحثًا عن معاملات قابلة للحقن. sqlmap خصوصًا أداة استغلال: تستخرج البيانات حين تجد ثغرة. شغّلها فقط على أنظمة تملكها أو لديك إذن كتابي لاختبارها، ويُفضَّل نسخة staging ببيانات مزيّفة، لأن استجواباتها تكتب في السجلات، وتستطيع تغيير البيانات، وقد تُفعّل أنظمة مراقبتك. فحص موقع غيرك بلا إذن مخالفة قانونية في معظم البلدان، بغض النظر عن النية.
إن اختبرت عبر CDN خاصتك، توقّع أن يحجب WAF كثيرًا من الاستجوابات. هذا WAF يعمل كما ينبغي، لكنه يخفي أيضًا سلوك التطبيق الحقيقي، فاختبر التطبيق نفسه على staging وعامِل WAF كطبقة منفصلة.
# PHP: query calls that mention a variable (review each hit)
grep -rnE '(query|exec|prepare)\(.*\$[A-Za-z_]' --include='*.php' app/
# Laravel / Eloquent raw fragments: check each one has bindings
grep -rnE '(whereRaw|selectRaw|orderByRaw|havingRaw|DB::raw)\(' app/
# Python: f-strings or % formatting inside execute()
grep -rnE "execute\(\s*f['\"]|execute\(.*['\"]\s*%\s" --include='*.py' .
مكان WAF: طبقة، لا الحل
يفحص جدار حماية تطبيقات الويب كل طلب قبل أن يصل إلى تطبيقك ويحجب ما يشبه الهجمات. يترك حقن SQL أشكالًا يمكن التعرف عليها في سلاسل الاستعلام وأجسام النماذج، فهذا من الأشياء التي يُحسن WAF فعلها: يوقف الفاحصات التلقائية التي تستجوب كل موقع على الإنترنت، ويمنحك وقتًا حين يُفصَح عن إضافة ضعيفة قبل أن تستطيع ترقيعها.
يبقى مطابقة أنماط مقابل ثغرة تعيش في كودك. المهاجم المستهدف يشكّل الحمولة لتفادي الأنماط، وبعض نقاط الحقن، مثل حقول JSON بترميز غير معتاد، أو قيم تصل إلى استعلام عبر مهمة خلفية، أو الحقن من الدرجة الثانية، لا تبدو مشبوهة في طلب أبدًا. يخرّط دليل OWASP Top 10 ما يستطيع WAF تغطيته وما لا يستطيع. تبقى الاستعلامات المُعامَلة الحل.
التكلفة التشغيلية هي الإيجابيات الكاذبة. أي قاعدة صارمة بما يكفي لرصد الحقن ستحجب إنسانًا أحيانًا: محرر يحفظ منشورًا بنموذج كود، أو نموذج دعم يلصق فيه أحدهم رسالة خطأ تحتوي SQL. الممارسة المعتادة تشغيل القواعد الجديدة بوضع كشف (تسجيل فقط) أولًا، وقراءة ما كان سيُحجب، ثم التنفيذ، مع استثناءات ضيقة حيث يحمل حقل بشكل مشروع نصًّا شبيهًا بـSQL.
على CDN.com.tr، الـWAF هو ModSecurity مع OWASP Core Rule Set، يُفعَّل لكل حساب من صفحة قواعد التسليم. في تلك المجموعة، عائلة 942 هي قواعد حقن SQL. يحصل الزائر المحجوب على صفحة 403 بهوية العلامة التجارية مع رقم مرجعي، وهو معرّف الطلب في سجل التدقيق، فتستطيع رؤية أي قاعدة طابقت وعلى أي حقل بالضبط. تسرد صفحة سجل WAF في اللوحة الأحداث المحجوبة بفئة الهجوم والبلد وIP والقاعدة، وتُصدَّر إلى CSV أو XLSX، ويُظهر cdnctl waf logs نفس الشيء من سطر الأوامر. حين يُطلق حقل مشروع قاعدة، ينبغي أن يكون الحل ضيقًا: تلك القاعدة، على ذلك الحقل، على ذلك المسار، بدل إيقاف الحماية عن الموقع كله. الاستثناءات لمسارات مفردة ليست في اللوحة بعد، فأرسل الرقم المرجعي للطلب إلى الدعم. لنماذج تسجيل الدخول، يقع تحديد معدل الطلبات لكل مسار على الصفحة نفسها، ويُبطئ حركة القوة الغاشمة التي ترافق غالبًا فحوص الحقن.
أسئلة شائعة حول حقن SQL
هل حقن SQL لا يزال مشكلة في 2026؟
نعم. تجعل أطر العمل الحديثة المسار الآمن الافتراضي، لكن الاستعلامات الخام، والكود القديم، والإضافات، والأدوات الداخلية السريعة لا تزال تُجمِّع النصوص، وتظهر وتُستغل ثغرات حقن جديدة في منتجات واسعة الاستخدام على نطاق واسع باستمرار. ظهر الحقن في كل إصدار من OWASP Top 10.
هل تمنع الجمل المُعدّة مسبقًا كل حقن SQL؟
تمنع الحقن عبر كل قيمة تربطها. لا تستطيع ربط مُعرّفات مثل أسماء الجداول أو الأعمدة أو اتجاه الترتيب، ولا تساعد إن بنيت نصًّا أولًا ثم "حضّرت" النتيجة. ضع المُعرّفات في قائمة سماح ولا تُنسِّق إدخالًا داخل نص SQL أبدًا.
هل استخدام ORM يجعلني بمنأى عن حقن SQL؟
غالبًا، للاستعلامات العادية. يملك كل ORM طرقًا خامًا، مثل whereRaw وDB::raw و.raw() وtext() أو FromSqlRaw، تمرر SQL دون تغيير. استخدمها بروابط، وراجع كل استدعاء.
هل التحقق من الإدخال كافٍ لإيقاف حقن SQL؟
لا. التحقق خط دفاع ثانٍ مفيد: المعرّف يجب أن يكون رقميًا والتاريخ يجب أن يُحلَّل. لكن كثيرًا من الحقول يجب أن تقبل علامات تنصيص ونصًّا حرًّا، فلا يمكن أن يكون التحقق هو الحماية. الاستعلامات المُعامَلة هي الحماية.
هل يستطيع WAF إيقاف حقن SQL تمامًا؟
يوقف معظم المحاولات التلقائية ويرفع الجهد المطلوب للمحاولات المستهدفة، لكنه يطابق أنماطًا في الطلبات ويستطيع مهاجم مصمّم تشكيل حمولة لتفاديها. الحقن من الدرجة الثانية لا يبدو مشبوهًا في طلب أبدًا. استخدم WAF كعمق، مع الاستعلامات المُعامَلة كحل.
هل اختبار موقع بـsqlmap قانوني؟
فقط على أنظمة تملكها أو لديك إذن كتابي صريح لاختبارها. فحص موقع غيرك وصول غير مُصرَّح به في معظم الولايات القضائية. اختبر بيئة staging خاصة ببيانات مزيّفة؛ وإن وجدت ثغرة في منتج غيرك، أبلغ عنها عبر مسار الإفصاح الخاص بهم.
ما الفرق بين حقن SQL وXSS؟
يجعل حقن SQL قاعدة بياناتك تُشغّل SQL كتبه المهاجم. وتجعل البرمجة العابرة للمواقع متصفح الزائر يُشغّل JavaScript كتبه المهاجم في سياق موقعك. كلاهما حقن، بنفس السبب الجذري وهو مزج البيانات بالكود، ويحتاج كلّ منهما حله الخاص: استعلامات مُعامَلة لـSQL، وترميز إخراج يراعي السياق لـHTML. انظر ما هو XSS؟.