مقدمة: هل يجب أن تخاف من mysql_real_escape_string()؟
\n\nربما سمعت المقولة القديمة لمطوري PHP: «mysql_real_escape_string() تحمي من جميع هجمات حقن SQL». هل هذا صحيح حقًا؟ في عام 2024، عندما أصبحت PDO والاستعلامات المُعدة (prepared statements) هي المعيار، تبدو هذه القضية قديمة. لكن آلاف المطورين ما زالوا يرثون مشاريع قديمة حيث mysql_real_escape_string() هي خط الدفاع الوحيد.
على Stack Overflow، حصد سؤال «SQL injection that gets around mysql_real_escape_string()» مئات الآلاف من المشاهدات. لماذا؟ لأن الإجابة ليست واضحة. في هذا المقال، سنستعرض سيناريوهات حقيقية لا تنجح فيها عملية التهريب، ونقارن بين طرق الحماية المختلفة. إذا كنت تكتب بلغة PHP، اقرأ بتركيز.
\n\nكيف تعمل mysql_real_escape_string()
\n\nالدالة mysql_real_escape_string() (من الإضافة mysql_*، والتي تم إهمالها الآن) تقوم بتهريب الأحرف الخاصة في النص، مع مراعاة ترميز الاتصال الحالي. تضيف شرطة مائلة عكسية قبل الأحرف: ', ", \\, \\n, \\r, NULL و Control-Z.
<?php\n$connection = mysql_connect('localhost', 'user', 'pass');\nmysql_select_db('test', $connection);\n$unsafe = "O'Brien";\n$safe = mysql_real_escape_string($unsafe); // O\\'Brien\n$query = "SELECT * FROM users WHERE name = '$safe'";\n?>\n\nيبدو أن هذا يعمل. لكن هناك تفاصيل دقيقة.
\n\nالسيناريو 1: مشكلة الترميز (GBK / Big5)
\n\nأشهر طريقة للتجاوز هي استخدام الترميزات متعددة البايت، مثل GBK (الصينية) أو Big5. إذا تم ضبط الاتصال على GBK، وقمت بإرسال النص %bf%27 — تحدث سحرية.
في GBK، البايت 0xbf ليس حرفًا مكتملاً. عندما تواجه mysql_real_escape_string() علامة الاقتباس ' (0x27)، تضيف شرطة مائلة عكسية 0x5c. النتيجة هي التسلسل: 0xbf + 0x5c + 0x27. لكن في GBK، 0xbf5c هو حرف واحد صالح (مثل الحرف الصيني «縗»). ونتيجة لذلك، «تُبتلع» الشرطة المائلة، وتبقى علامة الاقتباس غير محمية!
<?php\n// ضبط الترميز على GBK\nmysql_query("SET NAMES 'GBK'", $connection);\n$input = "%bf%27"; // البايتات: 0xbf 0x27\n$escaped = mysql_real_escape_string($input, $connection);\n// النتيجة: 0xbf5c27 -> في GBK هذا هو "縗'" (علامة الاقتباس بقيت)\n$query = "SELECT * FROM users WHERE id = '$escaped'";\n// في الواقع يتم تنفيذ: SELECT * FROM users WHERE id = '縗''\n// الآن يمكن حقن: ' OR 1=1 -- \n?>\n\nالخلاصة: إذا كان ترميز الاتصال لا يتطابق مع ترميز البيانات، يمكن تجاوز mysql_real_escape_string(). هذا هو الخلل نفسه الموصوف في تقرير الخلل الشهير PHP #47168.
السيناريو 2: استخدام الحقول الرقمية
\n\nالتهريب يعمل فقط مع النصوص. إذا قمت بإدخال بيانات في حقل رقمي بدون علامات اقتباس، فإن mysql_real_escape_string() لن تساعد، لأن القيمة لا يتم تهريبها — بل تُدرج كما هي.
<?php\n$id = mysql_real_escape_string($_GET['id']); // لنفترض "1 OR 1=1"\n$query = "SELECT * FROM users WHERE id = $id"; // بدون علامات اقتباس!\n// الخطر: SELECT * FROM users WHERE id = 1 OR 1=1\n?>\n\nحتى لو قمت بتهريب النص، في السياق الرقمي لا معنى لذلك — لا توجد علامات اقتباس، وبالتالي يمكن إدخال أي كود SQL.
\n\nالخلاصة: التهريب لا يغني عن التحقق من نوع البيانات. للأرقام، استخدم intval() أو ctype_digit().
السيناريو 3: استعلامات LIKE وأحرف البدل (wildcard)
\n\nmysql_real_escape_string() تهرب ' و \\، لكنها لا تلمس الأحرف