حقن SQL وتجاوز mysql_real_escape_string(): خرافات وحقائق

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: هل يجب أن تخاف من mysql_real_escape_string()؟

\n\n

ربما سمعت المقولة القديمة لمطوري PHP: «mysql_real_escape_string() تحمي من جميع هجمات حقن SQL». هل هذا صحيح حقًا؟ في عام 2024، عندما أصبحت PDO والاستعلامات المُعدة (prepared statements) هي المعيار، تبدو هذه القضية قديمة. لكن آلاف المطورين ما زالوا يرثون مشاريع قديمة حيث mysql_real_escape_string() هي خط الدفاع الوحيد.

\n\n

على 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.

\n\n
<?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 — تحدث سحرية.

\n\n

في GBK، البايت 0xbf ليس حرفًا مكتملاً. عندما تواجه mysql_real_escape_string() علامة الاقتباس ' (0x27)، تضيف شرطة مائلة عكسية 0x5c. النتيجة هي التسلسل: 0xbf + 0x5c + 0x27. لكن في GBK، 0xbf5c هو حرف واحد صالح (مثل الحرف الصيني «縗»). ونتيجة لذلك، «تُبتلع» الشرطة المائلة، وتبقى علامة الاقتباس غير محمية!

\n\n
<?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.

\n\n

السيناريو 2: استخدام الحقول الرقمية

\n\n

التهريب يعمل فقط مع النصوص. إذا قمت بإدخال بيانات في حقل رقمي بدون علامات اقتباس، فإن mysql_real_escape_string() لن تساعد، لأن القيمة لا يتم تهريبها — بل تُدرج كما هي.

\n\n
<?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().

\n\n

السيناريو 3: استعلامات LIKE وأحرف البدل (wildcard)

\n\n

mysql_real_escape_string() تهرب ' و \\، لكنها لا تلمس الأحرف

المدونات

توصيات الكتب