مقدمة: الخرافات والواقع حول PDO
\n\nإذا كنت تكتب بلغة PHP ولو مرة بحثت في Google عن "كيف تحمي نفسك من حقن SQL"، فبالتأكيد سمعت العبارة المتداولة: "استخدم PDO والعبارات المعدة - وستكون سعيدًا". هذا هو المعيار الذهبي، أساس أي كتاب عن أمان PHP. ولكن هل هذا صحيح حقًا؟ هل يكفي فقط كتابة $stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?') ونسيان المشكلة؟
السؤال "هل عبارات PDO المعدة كافية لمنع حقن SQL؟" حصد آلاف المشاهدات على Stack Overflow ليس من دون سبب. الإجابة، كما تبين، ليست بهذه البساطة. في هذه المقالة، سنتعمق تحت غطاء PDO، ونحلل كيف تعمل الاستعلامات المعدة فعليًا، ونكتشف في أي الحالات قد يخذلك PDO.
\n\nهذه ليست نظرية مملة. سننظر في أمثلة حقيقية، ومحاكاة الاستعلامات، وفروقات الترميز، وسنقدم إجراءات عملية محددة ليكون كودك محميًا حقًا.
\n\nكيف تعمل العبارات المعدة: سحر أم رياضيات؟
\n\nأولاً، لنتذكر ما هو حقن SQL. هو عندما يمرر المهاجم بيانات إلى الاستعلام يتم تفسيرها كجزء من كود SQL. الكلاسيكية: ' OR 1=1 --.
العبارات المعدة تحل هذه المشكلة بشكل جذري. فهي تفصل منطق الاستعلام عن البيانات. يبدو الأمر هكذا:
\n\n<?php\n$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');\n$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email AND status = :status');\n$stmt->execute([':email' => $_GET['email'], ':status' => 'active']);\n?>\n\n\nما يحدث تحت الغطاء هو التالي:
\n\n- \n
- التحضير (Prepare): يرسل المُشغّل إلى خادم قاعدة البيانات "هيكل" الاستعلام:
SELECT * FROM users WHERE email = ? AND status = ?. يقوم الخادم بتحليل هذا الهيكل، وتحسينه، وحفظ خطة التنفيذ. مكان البيانات (العلامات المكانية) يبقى فارغًا حاليًا. \n - التنفيذ (Execute): الآن ترسل فقط القيم الفعلية. يقوم الخادم بإدراجها في الخطة الجاهزة، لكنه يفعل ذلك بطريقة خاصة - فهو يعلم أن هذه بيانات وليست كودًا. لذلك، أي محاولة "لإغلاق علامة الاقتباس" أو إضافة أمر SQL تتحول ببساطة إلى جزء من النص الحرفي. \n
يبدو مثاليًا. لكن هناك فارق بسيط.
\n\nالمزلق: محاكاة العبارات المعدة
\n\nالمفاجأة الرئيسية التي كشفها ذلك السؤال على Stack Overflow: PDO لا يستخدم دائمًا العبارات المعدة "الحقيقية" بشكل افتراضي.
\n\nالسبب هو أن ليست كل قواعد البيانات تدعم هذه الميزة على مستوى البروتوكول. وبعض المُشغّلات (مثل SQLite أو الإصدارات القديمة من MySQL) تعمل من خلال المحاكاة. ماذا يعني هذا؟
\n\nعندما تكون المحاكاة مفعلة، يقوم PDO بنفسه بإدراج القيم في استعلام SQL من جانب العميل (في PHP)، ثم يرسل الاستعلام الجاهز إلى الخادم. وفي هذه العملية، يقوم بتهريب الأحرف الخاصة باستخدام PDO::quote().
وهنا تبدأ المشاكل. المحاكاة هي، في جوهرها، نفس عملية إدراج النصوص، ولكن يتم تنفيذها بواسطة المكتبة. وإذا قمت بتعيين ترميز الاتصال بشكل غير صحيح، يمكن اختراق هذه الآلية.
\n\nمثال: هجوم عبر ترميزات مختلفة
\n\nالهجوم الكلاسيكي باستخدام ترميز غير صحيح يبدو هكذا. لنفترض أنك قمت بتعيين اتصال MySQL بترميز GBK (ترميز صيني)، والاستعلام نفسه يتم محاكاته. الرمز \\ (الشرطة المائلة العكسية) في GBK قد يكون جزءًا من رمز متعدد البايت.
يمرر المهاجم السلسلة: %BF%27. في ترميز GBK، التسلسل %BF%5C يشكل رمزًا واحدًا، بينما %27 هو مجرد علامة اقتباس. يرى المُحاكي علامة الاقتباس ويقوم بتهريبها، محولاً %27 إلى %5C%27 (أي \\'). ولكن بسبب أن