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





14 Answers

تحذير: رمز عينة هذا الجواب (مثل رمز عينة السؤال) يستخدم ملحق 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 مصدر مفيد في بعض المشكلات الأكثر دقة التي يمكن أن تواجهها.
php mysql sql security sql-injection

إذا تم إدخال إدخال المستخدم دون تعديل في استعلام 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;--')

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




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

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

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




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

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

مقاربتي:

  • إذا كنت تتوقع أن يكون الإدخال عددًا صحيحًا ، فتأكد من أنه صحيح بالفعل . في لغة من نوع متغير مثل PHP من المهم جدا . يمكنك استخدام هذا الحل البسيط جدًا على سبيل المثال: sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • إذا كنت تتوقع أي شيء آخر من عرافة صحيح ذلك . إذا كنت ستعطيه ، فسوف تفلت تماما من كل المدخلات. في C / C ++ هناك وظيفة تسمى mysql_hex_string() ، في PHP يمكنك استخدام bin2hex() .

    لا تقلق من أن السلسلة المهربة سيكون لها حجم 2x من طولها الأصلي لأنه حتى لو كنت تستخدم mysql_real_escape_string ، يجب على PHP تخصيص نفس السعة ((2*input_length)+1) ، وهو نفس الشيء.

  • غالبًا ما تستخدم هذه الطريقة السداسية عند نقل البيانات الثنائية ، ولكن لا أرى أي سبب لعدم استخدامها على جميع البيانات لمنع هجمات حقن SQL. لاحظ أنه يجب عليك إرسال البيانات UNHEX 0x إلى 0x أو استخدام الدالة MySQL ، بدلاً من ذلك ، بدلاً من ذلك.

لذلك ، على سبيل المثال ، الاستعلام:

SELECT password FROM users WHERE name = 'root'

سيصبح:

SELECT password FROM users WHERE name = 0x726f6f74

أو

SELECT password FROM users WHERE name = UNHEX('726f6f74')

الهيكس هو الهروب المثالي. لا توجد وسيلة لحقن.

الفرق بين وظيفة ال UNEX والبادئة 0x

كان هناك بعض النقاش في التعليقات ، لذا أود في النهاية توضيح ذلك. هذان الأسلوبان متشابهان للغاية ، ولكنهما مختلفان بعض الشيء من بعض النواحي:

يمكن استخدام البادئة ** 0x ** لأعمدة البيانات فقط مثل char أو varchar أو text أو block أو binary أو غير ذلك .
كما أن استخدامه معقد بعض الشيء إذا كنت على وشك إدخال سلسلة فارغة. سيكون عليك استبدالها تمامًا بـ '' ، أو ستحصل على خطأ.

تعمل UNEX () على أي عمود ؛ لا داعي للقلق حول السلسلة الفارغة.

غالبًا ما تُستخدم الطرق السداسية باعتبارها هجمات

لاحظ أن هذا الأسلوب السداسي غالبًا ما يتم استخدامه كمهاجم حقن SQL حيث الأعداد الصحيحة تشبه السلاسل والفرار فقط مع mysql_real_escape_string . ثم يمكنك تجنب استخدام الاقتباسات.

على سبيل المثال ، إذا كنت تفعل شيء كهذا:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

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

SELECT ... WHERE id = -1 union all select table_name from information_schema.tables

والآن فقط استخراج هيكل الجدول:

SELECT ... WHERE id = -1 union all select column_name from information_schema.column where table_name = 0x61727469636c65

ثم اختر فقط البيانات التي تريدها. أليس هذا رائع؟

ولكن إذا كان المبرمج في موقع قابل للحقن عشريًا ، فلن يكون هناك أي حقن ممكنًا لأن الاستعلام سيبدو كما يلي: SELECT ... WHERE id = UNHEX('2d312075...3635')




تحذير أمان : لا يتوافق هذا الجواب مع أفضل ممارسات الأمان. الهروب غير ملائم لمنع إدخال 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 ، على الرغم من استخدام mysql_real_escape_string() .

هذه الأمثلة عرضة لحقن SQL:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

أو

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

في كلتا الحالتين ، لا يمكنك استخدام ' لحماية التغليف.

Source : حقن SQL غير متوقع (عند الفرار غير كاف)




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

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

العيوب -

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



أعتقد أنه إذا كان هناك من يريد استخدام 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٪ للبيانات المعدّة.

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




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

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




بالنسبة لأولئك غير متأكدين من كيفية استخدام PDO (قادمين من mysql_الوظائف) ، فقد قمت بعمل غلاف PDO بسيط جدا وهو ملف واحد. إنه موجود لإظهار مدى سهولة القيام بكل الأشياء الشائعة التي تحتاجها التطبيقات. يعمل مع PostgreSQL و MySQL و SQLite.

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

اريد عمود واحد

$count = DB::column('SELECT COUNT(*) FROM `user`);

أريد الحصول على نتائج صفيف (مفتاح => قيمة) (على سبيل المثال ، لإنشاء مربع اختيار)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

اريد نتيجة صف واحد

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

اريد مجموعة من النتائج

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));



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

 GRANT SELECT, INSERT, DELETE ON database TO username@'localhost' IDENTIFIED BY 'password';

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

FLUSH PRIVILEGES; 

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

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

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

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




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




** تحذير: النهج الموصوف في هذه الإجابة ينطبق فقط على سيناريوهات محددة جدًا وغير آمن نظرًا لأن هجمات حقن 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 للمهاجم ، أو حتى كوكبها ، أو سجلها ، أو متصفحها ، أو أي شيء آخر حساس المعلومات ، حتى تتمكن من التعامل معهم في وقت لاحق عن طريق حظر حساباتهم أو الاتصال بالسلطات.




من الأفكار الجيدة استخدام " أداة تخطيط الكائنات ذات الصلة" مثل 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 ولكن من أخطاء بناء الجملة أيضا! كما يدعم مجموعات من النماذج مع تسلسل الأسلوب لتصفية أو تطبيق الإجراءات على نتائج متعددة في وقت واحد وصلات متعددة.




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

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لا يتسبب هذا الإصدار في حدوث مشكلات إذا كان الاستبدال يحتوي على هذه الرموز المميزة.




Related