مقدمة: السؤال الذي يقلق كل مطور PHP
\n\nإذا سبق لك كتابة استعلامات SQL في PHP، فبالتأكيد سمعت هذه المقولة: "استخدم PDO والاستعلامات المحضرة وستكون في أمان تام". ولكن هل هذا صحيح حقًا؟ على Stack Overflow، سؤال "Are PDO prepared statements sufficient to prevent SQL injection?" حصد آلاف المشاهدات وعشرات النقاشات الساخنة. وهذا ليس بدون سبب.
\n\nفي هذا المقال، لن نقدم إجابة مختصرة بـ"نعم" أو "لا". بل سنتعمق في آلية عمل PDO، ونفهم كيف تعمل الاستعلامات المحضرة بالضبط، ونكتشف الحالات التي قد يخذلك فيها PDO. هل أنت مستعد؟ فلنبدأ.
\n\nما هو حقن SQL ولماذا هو خطير؟
\n\nقبل الحديث عن الحماية، دعنا نتعرف على العدو. حقن SQL هو هجوم يقوم فيه المخترق بحقن كود SQL ضار عبر بيانات الإدخال. المثال الكلاسيكي:
\n\n// كود سيء: لا تفعل هذا أبدًا\n$username = $_POST['username'];\n$query = "SELECT * FROM users WHERE username = '$username'";\n$result = mysqli_query($conn, $query);\n\n\nإذا أدخل المخترق admin' OR '1'='1، يتحول الاستعلام إلى:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n\n\nوهكذا يتم الوصول إلى البيانات بدون كلمة مرور. وإذا أدخل '; DROP TABLE users; --؟ وداعًا لجدول المستخدمين.
PDO والاستعلامات المحضرة: كيف تعمل؟
\n\nPDO (PHP Data Objects) يوفر آلية prepared statements (الاستعلامات المحضرة). الفكرة بسيطة: تفصل هيكل الاستعلام عن البيانات. بدلاً من إدخال المتغيرات مباشرة في النص، تستخدم عناصر نائبة (placeholders):
\n\n// كود آمن باستخدام PDO\n$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");\n$stmt->execute([':username' => $_POST['username']]);\n\n\nولكن ماذا يحدث تحت الغطاء؟ هذه هي النقطة الأساسية التي يغفل عنها الكثيرون.
\n\nوضع المحاكاة مقابل الاستعلامات المحضرة الأصلية
\n\nيمكن لـ PDO العمل في وضعين:
\n\n- \n
- وضع المحاكاة (الافتراضي في الإصدارات القديمة من PHP) — يقوم PDO بتنظيف البيانات وإدراجها في الاستعلام من جانب PHP. هذا لا يزال آمنًا، لكنه قد يكون عرضة للاختراق نظريًا إذا تعطلت عملية التنظيف (مثلًا عند التعامل مع ترميزات غير قياسية). \n
- الوضع الأصلي — يرسل PDO رسالتين إلى خادم قاعدة البيانات: الأولى تحتوي على قالب الاستعلام، والثانية تحتوي على البيانات بشكل منفصل. يتعامل الخادم مع البيانات كقيم وليس ككود. هذا آمن تمامًا. \n
لتفعيل الوضع الأصلي، قم بتعطيل المحاكاة:
\n\n$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);\n\n\nالآن يتم إرسال الاستعلام إلى الخادم على مرحلتين. حتى لو أدخل المخترق ' OR '1'='1، فإن خادم MySQL أو PostgreSQL سيتعامل معها كقيمة نصية وليس كجزء من كود SQL. سحر!
ولكن هل كل شيء مثالي؟ عيوب PDO الخفية
\n\nلنعد إلى سؤال Stack Overflow. الإجابة "نعم، استعلامات PDO المحضرة كافية" — ولكن مع تحفظات. إليك الحالات التي قد تفشل فيها الحماية.
\n\n1. المعرفات الديناميكية (أسماء الجداول والأعمدة)
\n\nالاستعلامات المحضرة تعمل فقط مع القيم (البيانات). إذا كنت تدرج اسم جدول أو عمود، فلن تساعدك العناصر النائبة:
\n\n// خطير! لا يمكن تمرير اسم الجدول عبر عنصر نائب\n$stmt = $pdo->prepare("SELECT * FROM :table WHERE id = :id");\n$stmt->execute([':table' => $_GET['table'], ':id' => 1]); // خطأ!\n\n\nسيقوم PDO بإدراج اسم الجدول كنص، وسيفشل الاستعلام. وإذا كنت تستخدم وضع المحاكاة، يمكن للمخترق إدخال users; DROP TABLE users; --. الحل: قائمة بيضاء من القيم المسموح بها.
2. استعلامات LIKE ورموز الاستبدال
\n\nإذا كنت تستخدم