كيف يمكنني منع حقن SQL في PHP؟




mysql security (19)

تحذير أمان : لا يتوافق هذا الجواب مع أفضل ممارسات الأمان. الهروب غير ملائم لمنع إدخال SQL ، استخدم العبارات المعدة بدلاً من ذلك. استخدم الاستراتيجية الموضحة أدناه على مسؤوليتك الخاصة. (أيضًا ، تمت إزالة mysql_real_escape_string() في PHP 7.)

يمكنك فعل شيء أساسي مثل هذا:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

هذا لن يحل كل مشكلة ، لكنه نقطة انطلاق جيدة جدًا. لقد تركت عناصر واضحة مثل التحقق من وجود المتغير والشكل (الأرقام والحروف وما إلى ذلك).

إذا تم إدخال إدخال المستخدم دون تعديل في استعلام SQL ، فسيصبح التطبيق عرضة لحقن SQL ، كما في المثال التالي:

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

هذا لأنه يمكن للمستخدم إدخال شيء مثل value'); DROP TABLE table;-- value'); DROP TABLE table;-- ويصبح الاستعلام:

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

ما الذي يمكن فعله لمنع حدوث ذلك؟


تحذير: رمز عينة هذا الجواب (مثل رمز عينة السؤال) يستخدم ملحق mysql الخاص بـ PHP ، والذي تم إيقافه في PHP 5.5.0 وإزالته بالكامل في PHP 7.0.0.

إذا كنت تستخدم إصدارًا حديثًا من PHP ، فلن يكون خيار mysql_real_escape_string الموضح أدناه متاحًا (على الرغم من أن mysqli::escape_string مكافئ حديث). في هذه الأيام ، سيكون خيار mysql_real_escape_string منطقيًا للشفرة القديمة فقط في إصدار قديم من PHP.

لديك خياران - الهروب من الأحرف الخاصة في unsafe_variable ، أو باستخدام استعلام ذو معلمات. كلا من شأنه أن يحميك من حقن SQL. يعتبر الاستعلام ذو المعلمات أفضل الممارسات ولكنه سيتطلب تغيير ملحق mysql أحدث في PHP قبل أن تتمكن من استخدامه.

سنعمل على تغطية سلسلة التأثيرات المنخفضة أولاً.

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

انظر أيضًا ، تفاصيل وظيفة mysql_real_escape_string .

لاستخدام الاستعلام ذو المعلمات ، تحتاج إلى استخدام MySQLi بدلاً من وظائف MySQL . لإعادة كتابة المثال الخاص بك ، نحتاج إلى شيء مثل ما يلي.

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

سوف تكون الوظيفة الرئيسية التي ترغب في قراءتها في هناك mysqli::prepare .

أيضا ، كما اقترح آخرون ، قد تجد أنه من المفيد / الأسهل أن تصعد طبقة من التجريد بشيء مثل PDO .

يرجى ملاحظة أن القضية التي سألتم عنها هي حالة بسيطة إلى حد ما وأن الحالات الأكثر تعقيدًا قد تتطلب أساليب أكثر تعقيدًا. خاصه:

  • إذا كنت ترغب في تغيير بنية SQL استنادًا إلى إدخال المستخدم ، فلن تساعد الاستعلامات ذات المعلمات ، ولا يتم تغطية الهروب المطلوب من خلال mysql_real_escape_string . في هذا النوع من الحالات ، سيكون من الأفضل لك تمرير إدخال المستخدم من خلال قائمة بيضاء لضمان السماح فقط بالقيم "الآمنة".
  • إذا كنت تستخدم أعدادًا صحيحة من إدخال المستخدم في حالة ما mysql_real_escape_string منهج mysql_real_escape_string ، فستعاني من المشكلة التي وصفتها Polynomial في التعليقات أدناه. هذه الحالة أكثر تعقيدًا لأن الأعداد الصحيحة لن تكون محصورة بعلامات اقتباس ، لذا يمكنك التعامل معها من خلال التحقق من أن إدخال المستخدم يحتوي على أرقام فقط.
  • هناك حالات أخرى من المحتمل أنني لست على علم بها. قد تجد أن this مصدر مفيد في بعض المشكلات الأكثر دقة التي يمكن أن تواجهها.

أوصي باستخدام PDO (كائنات بيانات PHP) لتشغيل استعلامات SQL ذات معلمات.

هذا لا يحمي فقط من حقن SQL ، بل يسرع أيضا الاستعلامات.

وباستخدام PDO بدلاً من mysql_ ، و pgsql_ ، ووظائف pgsql_ ، فإنك تجعل تطبيقك أكثر تجريدًا من قاعدة البيانات ، في الحدوث النادر الذي يجب عليك تبديل مزودي قواعد البيانات.


استخدم PDO والاستعلامات المعدة.

( $conn هو كائن PDO )

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

في رأيي ، أفضل طريقة لمنع حقن SQL في تطبيق PHP الخاص بك (أو أي تطبيق ويب ، لهذا الأمر) هو التفكير في بنية التطبيق الخاص بك. إذا كانت الطريقة الوحيدة للحماية من حقن SQL هي تذكر استخدام طريقة خاصة أو وظيفة تقوم بـ "الشيء الصحيح" في كل مرة تتحدث فيها إلى قاعدة البيانات ، فأنت تقوم بذلك بشكل خاطئ. بهذه الطريقة ، إنها مجرد مسألة وقت حتى تنسى تنسيق الاستعلام الخاص بك بشكل صحيح في مرحلة ما من التعليمات البرمجية.

من المحتمل أن يكون تبني نمط MVC وإطار عمل مثل CakePHP أو CodeIgniter هو الطريقة الصحيحة: تم حل المهام الشائعة مثل إنشاء استعلامات قاعدة بيانات آمنة وتنفيذها مركزيًا في مثل هذه الأطر. فهي تساعدك على تنظيم تطبيق الويب الخاص بك بطريقة معقولة وتجعلك تفكر في تحميل الأشياء وحفظها أكثر من إنشاء استعلامات SQL واحدة بشكل آمن.


كل إجابة هنا تغطي فقط جزء من المشكلة. في الواقع ، هناك أربعة أجزاء استعلام مختلفة يمكننا إضافتها إليها ديناميكيًا:

  • سلسلة
  • رقم
  • معرف
  • كلمة أساسية في بناء الجملة.

وتغطي البيانات المعدة فقط اثنين منهم.

ولكن في بعض الأحيان يتعين علينا أن نجعل استعلامنا أكثر ديناميكية ، بإضافة عوامل التشغيل أو المعرفات أيضًا. لذا ، سنحتاج إلى أساليب حماية مختلفة.

بشكل عام ، يعتمد نهج الحماية هذا على القائمة البيضاء .

في هذه الحالة ، يجب أن تكون كل معلمة ديناميكية ضمنية في النص البرمجي ويتم اختيارها من هذه المجموعة. على سبيل المثال ، لتنفيذ الطلب الديناميكي:

$orders  = array("name", "price", "qty"); // Field names
$key     = array_search($_GET['sort'], $orders)); // See if we have such a name
$orderby = $orders[$key]; // If not, first one will be set automatically. smart enuf :)
$query   = "SELECT * FROM `table` ORDER BY $orderby"; // Value is safe

ومع ذلك ، هناك طريقة أخرى لتأمين المعرفات - الهروب. طالما كان لديك مُعرّف مُقتبس ، يمكنك الهروب من backticks في الداخل عن طريق مضاعفة.

وكخطوة أخرى ، يمكننا استعارة فكرة رائعة حقًا عن استخدام عنصر نائب (وكيل يمثل القيمة الفعلية في الاستعلام) من البيانات المعدة وأخترع عنصر نائب من نوع آخر - عنصر نائب.

لذا ، لجعل القصة طويلة قصيرة: إنه عنصر نائب ، لا يمكن اعتبار العبارة الجاهزة كدائرة فضية.

لذلك ، يمكن صياغة توصية عامة طالما أنك تضيف أجزاء ديناميكية إلى طلب البحث باستخدام العناصر النائبة (وهذه العناصر النائبة تم معالجتها بشكل صحيح بالطبع) ، يمكنك التأكد من أن استعلامك آمن .

ومع ذلك ، هناك مشكلة مع الكلمات الأساسية لغوي SQL (مثل AND DESC و) ، ولكن يبدو أن القائمة البيضاء هي الطريقة الوحيدة في هذه الحالة.

تحديث

على الرغم من وجود اتفاق عام على أفضل الممارسات فيما يتعلق بحماية حقن SQL ، فلا يزال هناك العديد من الممارسات السيئة كذلك. وبعضها عميق الجذور في عقول مستخدمي PHP. على سبيل المثال ، في هذه الصفحة بالذات هناك (على الرغم من أنه غير مرئي لمعظم الزوار) أكثر من 80 إجابات محذوفة - تمت إزالتها جميعًا من قبل المجتمع بسبب الجودة السيئة أو الترويج للممارسات السيئة التي عفا عليها الزمن. والأسوأ من ذلك أن بعض الإجابات السيئة لا تُحذف ، بل تزدهر.

على سبيل المثال ، there(1) are(2) still(3) many(4) answers(5) many(4) answers(5) ، بما في ذلك share تقترح عليك الهروب اليدوي للسلسلة - وهو أسلوب قديم ثبت أنه غير آمن.

أو هناك إجابة أفضل قليلاً تقترح فقط طريقة أخرى لتنسيق السلسلة وحتى أنها تفتخر بأنها الدواء الشافي النهائي. بينما بالطبع ، ليس كذلك. هذه الطريقة ليست أفضل من تنسيق السلسلة العادي ، ومع ذلك فإنها تحتفظ بكل عيوبها: فهي قابلة للتطبيق على السلاسل فقط ، وكأي تنسيق يدوي آخر ، فهي بشكل أساسي اختيارية ، وتدبير غير إلزامي ، وعرضة لخطأ بشري من أي نوع.

أعتقد أن كل هذا بسبب خرافة قديمة جدا ، مدعومة من قبل مثل هذه السلطات مثل OWASP أو دليل PHP ، والتي تعلن المساواة بين أي "الهروب" والحماية من حقن SQL.

بغض النظر عن ما قاله دليل PHP للأعمار ، فإن *_escape_string بأي حال من الأحوال يجعل البيانات آمنة ولا يُقصد بها أبدًا. إلى جانب كونه لا جدوى لأي جزء من أجزاء SQL بخلاف السلسلة ، فإن الهروب اليدوي يكون خاطئًا ، لأنه دليل على عكس ما هو أوتوماتيكي.

و OWASP يجعل الأمر أكثر سوءا ، مؤكدا على الهروب من مدخلات المستخدم وهو محض هراء: لا ينبغي أن يكون هناك مثل هذه الكلمات في سياق حماية الحقن. كل متغير يحتمل أن تكون خطرة - بغض النظر عن المصدر! أو بعبارة أخرى - يجب أن يتم تنسيق كل متغير بشكل صحيح ليتم وضعه في استعلام - بغض النظر عن المصدر مرة أخرى. إنها الوجهة المهمة. في اللحظة التي يبدأ فيها المطور في فصل الأغنام عن الماعز (مع الأخذ في الاعتبار ما إذا كان هناك متغير معين هو "آمن" أم لا) ، فإنه يأخذ أول خطوة نحو الكارثة. ناهيك عن أن حتى الصياغة تقترح الهروب بالجملة عند نقطة الدخول ، تشبه ميزة المقولات السحرية للغاية - المحتقنة بالفعل ، التي تم إهمالها وإزالتها.

لذا ، على عكس أي "هروب" ، فإن البيانات المعدة هي المقياس الذي يحمي بالفعل من حقن SQL (عند الاقتضاء).

إذا كنت مازلت غير مقتنع ، فإليك شرحًا تفصيليًا كتبته ، دليل Hitchhiker's to Prevention SQL prevent ، حيث شرحت كل هذه الأمور بالتفصيل وحتى جمعت قسمًا مخصصًا بالكامل للممارسات السيئة وإفشاءها.


مهما كان ما تفعله في النهاية ، تأكد من أنك قمت بفحص المدخلات الخاصة بك التي لم تكن قد تعرضت بالفعل للتشويه من قبل magic_quotes أو بعض القمامة الحسنة الأخرى ، وإذا لزم الأمر ، قم بتشغيلها من خلال stripslashes أو ما stripslashes .


استخدم العبارات المعدة والاستعلامات ذات المعلمات. هذه هي عبارات SQL التي يتم إرسالها وتحليلها بواسطة خادم قاعدة البيانات بشكل منفصل عن أي معلمات. بهذه الطريقة من المستحيل أن يقوم المهاجم بحقن SQL الضارة.

لديك في الأساس خياران لتحقيق ذلك:

  1. باستخدام PDO (لأي برنامج تشغيل قاعدة بيانات مدعوم):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. باستخدام MySQLi (لـ MySQL):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

إذا كنت متصلاً بقاعدة بيانات بخلاف MySQL ، فهناك خيار ثاني خاص pg_prepare() التشغيل يمكنك الرجوع إليه (مثل pg_prepare() و pg_execute() لـ PostgreSQL). PDO هو الخيار الشامل.

بشكل صحيح إعداد الاتصال

لاحظ أنه عند استخدام PDO للوصول إلى قاعدة بيانات MySQL فإن البيانات الحقيقية لا يتم استخدامها بشكل افتراضي . لإصلاح هذا يجب عليك تعطيل مضاهاة البيانات المعدة. مثال على إنشاء اتصال باستخدام الشركة هو:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

في المثال أعلاه ، لا يكون وضع الخطأ ضروريًا تمامًا ، ولكن يُنصح بإضافته . بهذه الطريقة ، لن يتوقف البرنامج النصي مع وجود Fatal Error عندما يحدث خطأ ما. ويعطي المطور فرصة catch أي خطأ (ق) التي يتم throw ن كما PDOException ق.

ما هو إلزامي ، على أية حال ، هو أول سطر setAttribute() ، الذي يخبر PDO بتعطيل البيانات setAttribute() واستخدام عبارات جاهزة حقيقية . هذا يضمن أن البيان والقيم لا يتم تحليلهما بواسطة PHP قبل إرساله إلى خادم MySQL (مما يمنح المهاجم المحتمل أي فرصة لإدخال SQL الخبيثة).

على الرغم من أنه يمكنك تعيين charset في خيارات المُنشئ ، فمن المهم ملاحظة أن الإصدارات "الأقدم" من PHP (<5.3.6) تتجاهل بصمت معلمة مجموعة الأحرف في DSN.

تفسير

ما يحدث هو أن عبارة SQL التي تقوم بتمريرها prepare يتم تحليلها وتجميعها بواسطة خادم قاعدة البيانات. من خلال تحديد المعلمات (إما a أو a parameter :name like :name في المثال أعلاه) ، فإنك تخبر مشغل قاعدة البيانات الذي تريد التصفية عليه. ثم عند استدعاء execute ، يتم الجمع بين العبارة المعدة مع قيم المعلمات التي تحددها.

الشيء المهم هنا هو أن يتم دمج قيم المعلمات مع العبارة المترجمة ، وليس سلسلة SQL. يعمل حقن SQL عن طريق خداع البرنامج النصي إلى تضمين سلاسل خبيثة عند إنشاء SQL لإرسالها إلى قاعدة البيانات. لذلك عن طريق إرسال SQL الفعلي بشكل منفصل عن المعلمات ، يمكنك الحد من خطر ينتهي بشيء لم تكن تنوي. سيتم التعامل مع أي معلمات ترسلها عند استخدام عبارة معدة فقط كسلسلة (على الرغم من أن محرك قاعدة البيانات قد يقوم ببعض التحسين ، لذا قد تنتهي المعلمات كأرقام أيضًا ، بالطبع). في المثال أعلاه ، إذا كان متغير $name يحتوي على 'Sarah'; DELETE FROM employees 'Sarah'; DELETE FROM employees ستكون النتيجة ببساطة عبارة عن بحث عن السلسلة "'Sarah'; DELETE FROM employees" ، ولن ينتهي بك الأمر مع جدول فارغ .

فائدة أخرى لاستخدام العبارات المعدة مسبقًا هي أنه في حالة تنفيذ نفس العبارة عدة مرات في نفس الجلسة ، فسيتم تحليلها وتجميعها مرة واحدة فقط ، مما يمنحك بعض المكاسب السريعة.

أوه ، وبما أنك سألت عن كيفية القيام بذلك لإدخال ، إليك مثال (باستخدام PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

هل يمكن استخدام البيانات الجاهزة للاستعلامات الديناميكية؟

في حين أنه لا يزال بإمكانك استخدام عبارات معدّة لمعلمات طلب البحث ، فإن بنية الاستعلام الديناميكي نفسه لا يمكن أن تكون بارتريتية ولا يمكن أن تكون باراميتر الاستعلام معينة بارتريتريزيد.

بالنسبة إلى هذه السيناريوهات المحددة ، فإن أفضل ما يمكن فعله هو استخدام فلتر القائمة البيضاء الذي يقيد القيم المحتملة.

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

أعتقد أنه إذا كان هناك من يريد استخدام PHP و MySQL أو أي خادم DataBase آخر:

  1. فكر في تعلم PDO (كائنات بيانات PHP) - إنها طبقة الوصول إلى قاعدة البيانات التي توفر طريقة موحدة للوصول إلى قواعد بيانات متعددة.
  2. فكر في تعلم MySQLi
  3. استخدم وظائف PHP الأصلية مثل: strip_tags ، أو mysql_real_escape_string() ، أو إذا كانت رقمية متغيرة ، فقط (int)$foo. اقرأ المزيد عن نوع المتغيرات في PHP here . إذا كنت تستخدم مكتبات مثل PDO أو MySQLi ، فاستخدم دائمًا PDO::quote() و mysqli_real_escape_string() .

أمثلة المكتبات:

---- شركة تنمية نفط عمان

----- لا عناصر نائبة - ناضجة لحقن SQL! انها سيئة

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values ($name, $addr, $city)");

----- العناصر النائبة غير المسماة

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) values (?, ?, ?);

----- الاسماء النائبة

$request = $pdoConnection->("INSERT INTO parents (name, addr, city) value (:name, :addr, :city)");

--- MySQLi

$request = $mysqliConnection->prepare('
       SELECT * FROM trainers
       WHERE name = ?
       AND email = ?
       AND last_login > ?');

    $query->bind_param('first_param', 'second_param', $mail, time() - 3600);
    $query->execute();

ملاحظة :

PDO تفوز بهذه المعركة بكل سهولة. مع دعم اثنتي عشرة من برامج تشغيل قواعد البيانات المختلفة والمعلمات المسماة ، يمكننا تجاهل خسارة الأداء الصغيرة ، والتعود على API الخاص بها. من وجهة نظر أمنية ، كلاهما آمن طالما أن المطور يستخدمها بالطريقة التي من المفترض أن يتم استخدامها

ولكن في حين أن كل من PDO و MySQLi سريعان للغاية ، فإن MySQLi يعمل بشكل أسرع في المعايير - بشكل ضئيل - 2.5٪ للبيانات غير المعدة ، و 6.5٪ للبيانات المعدّة.

ويرجى اختبار كل استعلام لقاعدة البيانات الخاصة بك - إنها طريقة أفضل لمنع الحقن.


لقد كتبت هذه الوظيفة الصغيرة منذ عدة سنوات:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

يسمح هذا بتشغيل عبارات في C # -ish حرف واحد String.Format مثل:

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

يهرب النظر في نوع المتغير. إذا حاولت توجيه الجدول أو أسماء الأعمدة ، فستفشل لأنه يضع كل سلسلة بين علامتي تنصيص وهما بناء غير صحيح.

تحديث الأمان: str_replaceسمح الإصدار السابق بحقنات بإضافة {to} رموز مميزة إلى بيانات المستخدم. preg_replace_callbackلا يتسبب هذا الإصدار في حدوث مشكلات إذا كان الاستبدال يحتوي على هذه الرموز المميزة.


** تحذير: النهج الموصوف في هذه الإجابة ينطبق فقط على سيناريوهات محددة جدًا وغير آمن نظرًا لأن هجمات حقن SQL لا تعتمد فقط على القدرة على الحقن X=Y. **

إذا كان المهاجمون يحاولون اختراق النموذج عبر $_GETمتغير PHP أو باستخدام سلسلة استعلام URL ، فستتمكن من الإمساك بهم إذا لم يكونوا آمنين.

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

لأن 1=1، ، 2=2، 1=2، 2=1، 1+1=2، الخ ... هي الأسئلة الشائعة لقاعدة بيانات SQL للمهاجم. ربما يتم استخدامه أيضًا من خلال العديد من تطبيقات الاختراق.

ولكن يجب أن تكون حذراً ، ويجب ألا تقوم بإعادة كتابة استعلام آمن من موقعك. يعطيك الرمز أعلاه تلميحًا ، لإعادة كتابة أو إعادة توجيه (يعتمد ذلك عليك) على سلسلة استعلام ديناميكية خاصة بالقرصنة في صفحة تخزن عنوان IP للمهاجم ، أو حتى كوكبها ، أو سجلها ، أو متصفحها ، أو أي شيء آخر حساس المعلومات ، حتى تتمكن من التعامل معهم في وقت لاحق عن طريق حظر حساباتهم أو الاتصال بالسلطات.


أنا أؤيد الإجراءات المخزنة ( MySQL قد دعم الإجراءات المخزنة منذ 5.0 ) من وجهة نظر أمنية - المزايا -

  1. تمكن معظم قواعد البيانات (بما في ذلك MySQL ) وصول المستخدم إلى تقييد تنفيذ الإجراءات المخزنة. يفيد التحكم في الوصول للأمان ذي الحبيبات الدقيقة في منع تصعيد هجمات الامتيازات. هذا يمنع التطبيقات المخترقة من القدرة على تشغيل SQL مباشرة مقابل قاعدة البيانات.
  2. وهي تجرد استعلام SQL الأولي من التطبيق بحيث تتوفر معلومات أقل من بنية قاعدة البيانات للتطبيق. هذا يجعل من الصعب على الأشخاص فهم البنية الأساسية لقاعدة البيانات وتصميم الهجمات المناسبة.
  3. يقبلون المعلمات فقط ، لذلك توجد مزايا الاستعلامات المعلمات. بالطبع - IMO لا تزال تحتاج إلى تطهير الإدخال الخاص بك - خاصة إذا كنت تستخدم SQL الحيوية داخل الإجراء المخزن.

العيوب -

  1. من الصعب الحفاظ عليها (الإجراءات المخزنة) وتميل للتكاثر بسرعة كبيرة. وهذا يجعل إدارتها قضية.
  2. فهي ليست مناسبة للغاية للاستعلامات الديناميكية - إذا تم تصميمها لقبول الشفرة الديناميكية كمعلمات ، فعندئذ يتم إلغاء الكثير من المزايا.

إذا كنت ترغب في الاستفادة من محركات ذاكرة التخزين المؤقت، مثل Redis أو Memcached ، ربما يمكن أن يكون DALMP خيار. ويستخدم MySQLi نقية . تحقق من ذلك: طبقة تجريد قاعدة بيانات DALMP لـ MySQL باستخدام PHP.

أيضا ، يمكنك "تحضير" الحجج الخاصة بك قبل إعداد الاستعلام الخاص بك بحيث يمكنك بناء استعلامات ديناميكية وفي النهاية لديك استعلام بيانات جاهز بالكامل. طبقة تجريد قاعدة بيانات DALMP لـ MySQL باستخدام PHP.


باستخدام هذه الوظيفة PHP mysql_escape_string()يمكنك الحصول على منع جيد بطريقة سريعة.

فمثلا:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - يهرب سلسلة لاستخدامها في mysql_query

لمزيد من الوقاية ، يمكنك الإضافة في النهاية ...

wHERE 1=1   or  LIMIT 1

أخيرا تحصل على:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

فيما يتعلق بالعديد من الإجابات المفيدة ، آمل أن أضيف بعض القيم لهذا الموضوع. حقن SQL هو هجوم يمكن القيام به من خلال مدخلات المستخدم (المدخلات التي يملؤها المستخدم ثم تستخدم داخل الاستعلامات) ، فإن أنماط حقن SQL هي صيغة صحيحة لطلب البحث بينما يمكننا تسميتها: استفسارات سيئة لأسباب سيئة ، ونحن نفترض أنه قد يكون هناك أن يكون شخصًا سيئًا يحاول الحصول على معلومات سرية (تجاوز التحكم في الوصول) التي تؤثر على مبادئ الأمان الثلاثة (السرية والنزاهة والتوافر).

الآن ، نهدف إلى منع التهديدات الأمنية مثل هجمات حقن SQL ، والسؤال الذي يطرح (كيف نمنع هجوم حقن SQL باستخدام PHP) ، يكون أكثر واقعية ، تصفية البيانات أو مسح بيانات الإدخال هو الحال عند استخدام بيانات إدخال المستخدم داخل مثل الاستعلام ، باستخدام لغة PHP أو أي لغة برمجة أخرى ليست هي الحالة ، أو على النحو الموصى به من قبل المزيد من الأشخاص لاستخدام التكنولوجيا الحديثة مثل البيان المعد أو أي أدوات أخرى تدعم حاليًا منع حقن SQL ، فكر في أن هذه الأدوات غير متوفرة بعد الآن؟ كيف تقوم بتأمين طلبك؟

منهجي مقابل حقنة SQL هو: مسح بيانات إدخال المستخدم قبل إرسالها إلى قاعدة البيانات (قبل استخدامها داخل أي استعلام).

تصفية البيانات لـ (تحويل البيانات غير الآمنة إلى بيانات آمنة) ضع في اعتبارك أن PDO و MySQLi غير متاحين ، كيف يمكنك تأمين طلبك؟ هل تجبرني على استخدامها؟ ماذا عن اللغات الأخرى غير PHP؟ أفضّل تقديم أفكار عامة حيث يمكن استخدامها للحدود الأوسع نطاقاً وليس فقط لغوية معينة.

  1. مستخدم SQL (الحد من امتياز المستخدم): معظم عمليات SQL الشائعة هي (SELECT، UPDATE، INSERT) ، إذن ، لماذا تعطي امتياز UPDATE لمستخدم لا يتطلب ذلك؟ على سبيل المثال ، يستخدم تسجيل الدخول وصفحات البحث SELECT فقط ، فلماذا يستخدم مستخدمو DB في هذه الصفحات ذات الامتيازات العالية؟ RULE: لا تقم بإنشاء مستخدم قاعدة بيانات واحد لجميع الامتيازات ، لجميع عمليات SQL ، يمكنك إنشاء نظامك مثل (deluser ، selectuser ، updateuser) كأسماء مستخدمين للاستخدام السهل.

انظر مبدأ الأقل امتياز

  1. تصفية البيانات: قبل إنشاء أي إدخال مستخدم طلب بحث ، يجب التحقق من صحته وتصفيته ، وبالنسبة للمبرمجين ، من المهم تحديد بعض الخصائص لكل متغيرات إدخال المستخدم: نوع البيانات ونمط البيانات وطول البيانات . يجب التحقق من صحة الحقل الذي يمثل رقمًا بين (x و y) باستخدام قاعدة محددة تمامًا ، بالنسبة إلى حقل يمثل سلسلة (نص): النمط هو الحالة ، على سبيل المثال ، يجب أن يحتوي اسم المستخدم على بعض الأحرف فقط. zA-Z0-9_-.] يختلف الطول بين (x و n) حيث x و n (الأعداد الصحيحة ، x <= n). القاعدة: إنشاء مرشحات دقيقة وقواعد التحقق من الصحة هي أفضل الممارسات بالنسبة لي.

  2. استخدام أدوات أخرى: هنا ، سوف أتفق معك أيضًا على أن العبارة المعدة (الاستعلام المعلوم) والإجراءات المخزنة ، فإن العيوب هنا هي هذه الطرق تتطلب مهارات متقدمة لا توجد لمعظم المستخدمين ، الفكرة الأساسية هنا هي التمييز بين استعلام SQL والبيانات المستخدمة في الداخل ، يمكن استخدام كلا النهجين حتى مع البيانات غير الآمنة ، لأن بيانات إدخال المستخدم هنا لا تضيف أي شيء إلى الاستعلام الأصلي مثل (أي أو س = س). لمزيد من المعلومات ، يرجى قراءة ورقة الغش لـ OWASP SQL Protection Prevention .

الآن ، إذا كنت مستخدمًا متقدمًا ، فابدأ باستخدام هذا الدفاع كما تشاء ، ولكن بالنسبة للمبتدئين ، إذا لم يتمكنوا من تنفيذ الإجراء المخزن بسرعة وأعدوا البيان ، فمن الأفضل تصفية بيانات الإدخال قدر الإمكان.

أخيرًا ، لنفترض أن المستخدم يرسل هذا النص أدناه بدلاً من إدخال اسم المستخدم الخاص به:

[1] UNION SELECT IF(SUBSTRING(Password,1,1)='2',BENCHMARK(100000,SHA1(1)),0) User,Password FROM mysql.user WHERE User = 'root'

يمكن التحقق من هذه المدخلات في وقت مبكر دون أي بيان جاهز والإجراءات المخزنة ، ولكن لتكون في الجانب الآمن ، يبدأ استخدامها بعد تصفية بيانات المستخدم والتحقق من صحتها.

النقطة الأخيرة هي اكتشاف السلوك غير المتوقع الذي يتطلب المزيد من الجهد والتعقيد. لا يوصى به لتطبيقات الويب العادية. السلوك غير المتوقع في إدخال المستخدم أعلاه هو SELECT ، UNION ، IF ، SUBSTRING ، BENCHMARK ، SHA ، الجذر بمجرد اكتشاف هذه الكلمات ، يمكنك تجنب الإدخال.

UPDATE1:

وعلق أحد المستخدمين بأن هذه المشاركة غير مجدية ، حسنًا! في ما يلي ما OWASP.ORG :

الدفاعات الأساسية:

الخيار رقم 1: استخدام العبارات المعدة (الاستعلامات المعلمة)
الخيار رقم 2: استخدام الإجراءات المخزنة
الخيار رقم 3: الإفلات من جميع المدخلات المقدمة من المستخدم الدُفعات

الإضافية:

أيضًا فرض: امتياز أقل
أداء أيضًا: التحقق من إدخال القائمة البيضاء

كما تعلم ، يجب أن يكون الادعاء على مقالة مدعومًا بحجة صحيحة ، على الأقل إشارة واحدة! خلاف ذلك ، فإنه يعتبر بمثابة هجوم ومطالبة سيئة!

Update2:

من دليل PHP ، PHP: البيانات المعدة - دليل :

الهروب وإدخال SQL

سيتم التغلب على المتغيرات المرتبطة تلقائيًا من قبل الخادم. يقوم الخادم بإدراج قيم الهروب الخاصة بهم في الأماكن المناسبة في قالب العبارة قبل التنفيذ. يجب تقديم تلميح إلى الخادم لنوع المتغير المنضم ، لإنشاء تحويل مناسب. راجع وظيفة mysqli_stmt_bind_param () لمزيد من المعلومات.

أحيانًا يعتبر الهروب التلقائي للقيم داخل الخادم ميزة أمان لمنع إدخال SQL. يمكن تحقيق نفس الدرجة من الأمن باستخدام عبارات غير معدّة إذا تم إهمال قيم الإدخال بشكل صحيح.

Update3:

قمت بإنشاء حالات اختبار لمعرفة كيفية قيام PDO و MySQLi بإرسال الاستعلام إلى خادم MySQL عند استخدام العبارة المعدة:

شركة تنمية نفط عمان:

$user = "''1''"; //Malicious keyword
$sql = 'SELECT * FROM awa_user WHERE userame =:username';
$sth = $dbh->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
$sth->execute(array(':username' => $user));

سجل الاستعلام:

    189 Query SELECT * FROM awa_user WHERE userame ='\'\'1\'\''
    189 Quit

MySQLi:

$stmt = $mysqli->prepare("SELECT * FROM awa_user WHERE username =?")) {
$stmt->bind_param("s", $user);
$user = "''1''";
$stmt->execute();

سجل الاستعلام:

    188 Prepare   SELECT * FROM awa_user WHERE username =?
    188 Execute   SELECT * FROM awa_user WHERE username ='\'\'1\'\''
    188 Quit

من الواضح أن بيانًا جاهزًا يهرب أيضًا من البيانات ، ولا شيء آخر.

وكما هو مذكور في البيان أعلاه The automatic escaping of values within the server is sometimes considered a security feature to prevent SQL injection. The same degree of security can be achieved with non-prepared statements, if input values are escaped correctly، فإن هذا يثبت أن التحقق من صحة البيانات مثل intval()فكرة جيدة للقيم الصحيحة قبل إرسال أي استعلام ، بالإضافة إلى منع بيانات المستخدم الضارة قبل إرسال الاستعلام هو نهج صحيح وصحيح .

يرجى الاطلاع على هذا السؤال لمزيد من التفاصيل: ترسل PDO استعلام أولي إلى MySQL بينما ترسل Mysqli استعلامًا جاهزًا ، وكلاهما ينتج نفس النتيجة.

المراجع:

  1. SQL الغش ورقة الحقن
  2. حقن SQL
  3. أمن المعلومات
  4. المبادئ الامنية
  5. تأكيد صحة البيانات

من الأفكار الجيدة استخدام " أداة تخطيط الكائنات ذات الصلة" مثل Idiorm :

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

ليس فقط يوفر لك من حقن SQL ولكن من أخطاء بناء الجملة أيضا! كما يدعم مجموعات من النماذج مع تسلسل الأسلوب لتصفية أو تطبيق الإجراءات على نتائج متعددة في وقت واحد وصلات متعددة.


هناك العديد من الطرق للوقاية من حقن SQL واختراق SQL الأخرى. يمكنك العثور عليه بسهولة على الإنترنت (بحث Google). وبالطبع فإن PDO هي واحدة من الحلول الجيدة. لكني أود أن أقترح عليك بعض الوقاية الجيدة من حقن SQL.

ما هو حقن SQL وكيفية الوقاية

دليل PHP لحقن SQL

شرح مايكروسوفت لحقن SQL والوقاية في PHP

والبعض الآخر مثل منع حقن SQL مع MySQL و PHP

الآن ، لماذا تحتاج إلى منع الاستعلام الخاص بك من حقن SQL؟

أود إعلامك: لماذا نحاول منع إدخال SQL مع مثال قصير أدناه:

الاستعلام عن تطابق مصادقة تسجيل الدخول:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

الآن ، إذا وضع شخص ما (مخترق)

$_POST['email']= [email protected]' OR '1=1

وكلمة المرور أي شيء ....

سيتم تحليل الاستعلام في النظام فقط حتى:

$query="select * from users where email='[email protected]' OR '1=1';

سيتم تجاهل الجزء الآخر. ما الذي سيحدث؟ سيتمكن مستخدم غير مخول (مخترق) من تسجيل الدخول كمسؤول دون الحصول على كلمة المرور الخاصة به. الآن ، يمكنه فعل أي شيء يمكن للشخص المشرف / البريد الإلكتروني القيام به. ترى ، أنه أمر خطير للغاية إذا لم يتم منع حقن SQL.


هناك طريقة بسيطة لاستخدام إطار عمل PHP مثل CodeIgniter أو Laravel التي تحتوي على ميزات مدمجة مثل التصفية والسجل النشط بحيث لا تقلق بشأن هذه الفروق الدقيقة.


يمكن حل بديل بسيط لهذه المشكلة من خلال منح الأذونات المناسبة في قاعدة البيانات نفسها. على سبيل المثال: إذا كنت تستخدم قاعدة بيانات mysql ، فأدخل إلى قاعدة البيانات من خلال المحطة الطرفية أو واجهة المستخدم المقدمة واتبع هذا الأمر فقط:

 GRANT SELECT, INSERT, DELETE ON database TO [email protected]'localhost' IDENTIFIED BY 'password';

سيحد هذا من أن يقتصر المستخدم على طلب البحث المحدد فقط. قم بإزالة إذن الحذف ومن ثم لن يتم حذف البيانات من الاستعلام الذي يتم تشغيله من صفحة php. الشيء الثاني الذي يجب عمله هو مسح الامتيازات بحيث يقوم mysql بتحديث الأذونات والتحديثات.

FLUSH PRIVILEGES; 

مزيد من المعلومات حول flush .

لمشاهدة الامتيازات الحالية للمستخدم إطلاق الاستعلام التالي.

select * from mysql.user where User='username';

تعرف على المزيد حول GRANT .





sql-injection