در این قسمت به سراغ مبحثی میرویم که به SQL Injection مربوط است؛ این مبحث شامل نکات ریزی است که از دید اکثر برنامه نویسان دور میماند.
یکی از 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 باید پیروی دو قانون زیر باشید:
این دو قانون منجر به کد زیر می شود:
$table = "`".str_replace("`","``",$table)."`";
قبل از ادامه ی مطلب باید این کد را در دو مرحله توضیح بدهم:
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 راهی غیر از این نداریم.
در این قسمت متوجه شدیم استفاده از Placeholder ها به تنهایی برای امنیت پایگاه داده و وب سایت ما کافی نیست و نباید کورکورانه هر داده ای را از سمت کاربر قبول کنیم.همچنین راه حل های مناسب را برای فرار از این نوع خطر Injection ارائه دادیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.