میخوای وارد بازار کار بشی و محصولات خودت رو بفرشی همین الان ثبت نام کن
0

خطر تزریق SQL با وجود placeholder ها در PDO قسمت ۱۱

۶۷ / ۱۰۰

سلام به شما همراهان و دوستان محترم سایت آموزشی و فروشگاهی مدرسه کدنویسی به دوره ی آموزش PDO خوش آمدید.

در این دوره قصد داریم شما را با خطر تزریق SQL با وجود placeholder ها در PDO قسمت ۱۱ به صورت کامل آشنا کرده و نحوه استفاده از آن را به شما آموزش دهیم پس با من همراه باشید

 


Prepared statements ها و اسامی جدول ها

یکی از SQL Injection هایی که بسیاری از برنامه نویسان به آن توجه نمی کنند به شرح زیر است:

چند وقت پیش کدی در stackoverflow دیدم که به شکل زیر بود:

$params = [];
$setStr = "";
foreach ($data as $key => $value)
{
    if ($key != "id")
    {
        $setStr .= $key." = :".$key.","; 
    }
    $params[':'.$key] = $value;

}
$setStr = rtrim($setStr, ",");
$pdo->prepare("UPDATE users SET $setStr WHERE id = :id")->execute($params);

در ظاهر این کد مشکلی ندارد؛ بسیار راحت و ساده است و از یک آرایه، کوئری ما را می سازد به طوری که کلید ها مساوی نام ستون ها و مقادیر مساوی مقادیری هستند که در کوئری جایگزین می شوند.

این کد یک مشکل وحشتناک دارد!

حتما می گویید مشکل کجاست؟ مگر از placeholder ها استفاده نشده است؟ بله، داده ی ما امن است اما این کد داده های کاربر را گرفته و مستقیما به کوئری اضافه می کند! بله متغیر key$ مستقیما و بدون هیچ عملیاتی وارد کوئری می شود. این مسئله یعنی یک Injection! به مثال زیر توجه کنید:

<form method=POST>
<input type=hidden name="name=(SELECT'hacked!')WHERE`id`=1#" value="">
<input type=hidden name="name" value="Joe">
<input type=hidden name="id" value="1">
<input type=submit>
</form>
<?php
if ($_POST) {
$pdo = new PDO('mysql:dbname=test;host=localhost', 'root', '');
    $params = [];
    $setStr = "";
    foreach ($_POST as $key => $value)
    {
        if ($key != "id")
        {
            $setStr .= $key." = :".$key.","; 
        }
        $params[$key] = $value; 

    }
    $setStr = rtrim($setStr, ",");
    $pdo->prepare("UPDATE users SET $setStr WHERE id = :id")->execute($params);
}

این کد، کوئری زیر را تولید می کند:

UPDATE users SET name=(SELECT'hacked!')WHERE`id`=1# = :name=(SELECT'1')WHERE`id`=1#,name = :name WHERE id = :id

در این حالت تمام مواردی که بعد از # قرار بگیرند، کامنت در نظر گرفته می شوند (در SQL کامنت ها با # مشخص می شوند). بنابراین مقدار name به جای “Joe” با کلمه ی “!hacked”  تنظیم می شود. خیلی ترسناک به نظر نمی آید، درست است؟ مسئله این جاست که هر نوع Injection یک آسیب پذیری به حساب می آید و این مثال تنها برای تفهیم شما بود.

در واقعیت روش های بسیار بیشتری وجود دارند که با آن ها می توان خراب کاری های بیشتر انجام داد. شاید در آینده در مقاله ای جداگانه آن ها را ذکر کنم اما الان باید برگردیم به بحث اصلی خودمان!

متاسفانه PDO هیچ palceholder ای برای identifier ها (اسامی جدول ها و فیلد ها) ندارد بنابراین توسعه دهندگان باید به صورت دستی آن ها را قالب بندی کنند.

برای قالب بندی identifier ها در MySQL باید پیروی دو قانون زیر باشید:

  • identifier ها را بین دو ` قرار دهید (نام این علامت backtick است).
  • backtick ها را با دوبرابر کردنشان، escape دهید.

این دو قانون منجر به کد زیر می شود:

$table = "`".str_replace("`","``",$table)."`";

قبل از ادامه ی مطلب باید این کد را در دو مرحله توضیح بدهم:

  • مرحله ی اول (فنی): تابع ()str_replace را باید از قبل یاد گرفته باشید اما توضیح کوتاهی در مورد آن می دهم. این تابع ساختار کلی زیر را دارد:

str_replace(find,replace,string,count)

به کد زیر توجه کنید:

<!DOCTYPE html>
<html>
<body>

<?php
echo str_replace("world","Peter","Hello world!");
?>

<p>In this example, we search for the string "Hello World!", find the value "world" and then replace the value with "Peter".</p>

</body>
</html>

کد زیر ابتدا رشته ی “!Hello world” را پیدا می کند و سپس رشته ی “world” را با رشته ی “Peter” عوض می کند. بنابراین خروجی کد می شود:

Hello Peter!

  • مرحله ی دوم (کوئری): دوباره به کوئری نگاه کنید:
$table = "`".str_replace("`","``",$table)."`";

این کد ابتدا در نام جدول علامت ` را پیدا کرده و سپس آن را دو برابر می کند (یعنی “) سپس کل این رشته را داخل دو علامت ` می گذارد. بعد از انجام چنین قالب بندی، وارد کردن table$ در کوئری کاملا بی خطر است.

این قوانین برای پایگاه های داده ی دیگر متفاوت هستند اما نکته ای که شما باید در ذهن داشته باشید این است که جداکننده ها به تنهایی کافی نیستند و خود آن ها را نیز باید escape کرد.

همچنین همیشه سعی کنید identifier های پویا را با لیستی از مقادیر مجاز چک کنید:

$orders  = ["name","price","qty"]; //نام فیلد
$key     = array_search($_GET['sort'],$orders); // ای موجود است؟ name چک کنید که آیا چنین 
$orderby = $orders[$key]; //اگر وجود نداشت اولی به صورت خودکار انتخاب می شود
$query   = "SELECT * FROM `table` ORDER BY $orderby"; //مقدار ما امن است

همانطور که در خود کد به صورت کامنت توضیح داده ام این کد به جای اینکه کور کورانه هر مقداری را از کاربر دریافت کند، آن را با مقادیر از پیش تعیین شده می سنجد که ببیند آیا اصلا چنین مقداری مجاز است یا خیر.

همچنین همین روش را می توانیم برای دستورات INSERT و UPDATE نیز انجام دهیم (MySQL از دستور SET برای هر دوی این موارد پشتیبانی می کند):

$data = ['name' => 'foo', 'submit' => 'submit']; // data for insert
$allowed = ["name", "surname", "email"]; // allowed fields
$values = [];
$set = "";
foreach ($allowed as $field) {
    if (isset($data[$field])) {
        $set.="`".str_replace("`", "``", $field)."`". "=:$field, ";
        $values[$field] = $data[$field];
    }
}
$set = substr($set, 0, -2);

این کد تنها سری مناسب دستور SET را تولید میکند که فقط شامل فیلد ها و placeholder های مجاز است:

`name`=:foo

همچنین می توان آن را برای آرایه ی values$ در دستور ()execute استفاده کرد:

$stmt = $pdo->prepare("INSERT INTO users SET $set");
$stmt->execute($values);

ما هم قبول داریم که شکل کدها تمیز ترین شکل نخواهد شد اما شکل کدها در برابر اهمیت معنایی ندارد، مگر نه؟ در PDO راهی غیر از این نداریم.

 


خلاصه ی مقاله

تا اینجای کار با خطر تزریق SQL با وجود placeholder ها در PDO قسمت ۱۱ آشنا شدیم و ادامه سری آموزش اتصال شیء گرا به پایگاه داده ( PDO ) را دنبال کنید . برای مطالعه پست‌ها بیشتر ، ما را در مدرسه کدنویسی تلگرام  و یا در تی جوان اینستاگرام دنبال کنید.

نظراتتون رو زیر همین پست با ما به اشتراک بگذارید.
ارسال دیدگاه

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

×

سلام کاربر عزیز

هر سوالی دارید در این بخش هستیم تا شما رو راهنمائی کنیم

روی لوگومون کلیک کنید

× چطور میتونم کمکتون کنم؟