سلام و احترام خدمت شما همراهان روکسو! در جلسه ی قبل از دوره آموزشی مقابله با SQL Injection به بحث قوانین قالب بندی و قالب بندی دستی اشاره کردیم و متوجه شدیم که قالب بندی دستی اشکالات زیادی به همراه دارد اما به راه حلی نرسیدیم. در این قسمت قصد داریم راه حل آن را ارائه کنیم: Prepared statement ها!
مفهوم prepared statement ها به زبان ساده از این قرار است:
به جای وارد کردن داده ها به طور مستقیم به پایگاه داده، می توانیم از عناصری استفاده کنیم که جای داده را بگیرند و خودِ داده ها بعدا ارسال شوند.
در مثال زیر ما داده ها را به صورت مستقیم و از طریق یک متغیر وارد پایگاه داده می کنیم:
uName = getRequestString("username"); uPass = getRequestString("userpassword"); sql = 'SELECT * FROM Users WHERE Name ="' + uName + '" AND Pass ="' + uPass + '"'
اگر کاربر صادقانه فرم را پر کند مثالی شبیه به حالت زیر پیش می آید:
SELECT * FROM Users WHERE Name ="John Doe" AND Pass ="myPass"
اما اگر خرابکارانه کدی مثل کد زیر را وارد کند اوضاع متفاوت می شود:
SELECT * FROM Users WHERE Name ="" or ""="" AND Pass ="" or ""=""
این کوئری همیشه صحیح یا true است (منطق زبان SQL اینطور است) و اطلاعات جدول ما را در اختیار کاربر/هکر قرار خواهد داد. روش های تزریق SQL یا SQL Injection بسیار زیاد و بعضا پیچیده هستند. ما در اینجا تنها یک مثال ساده از آن را ذکر کردیم تا شما با این مبحث آشنا شوید.
این مفهوم کلی و ساده ی SQL Injection است. برای آشنایی بیشتر با این بحث به این مقاله که قبلا نوشته شده است مراجعه کنید.
قبل از هر چیز دوست دارم متوجه تفاوت دو موضوع اصلی بشوید:
قابلیت استفاده از prepared statement ها به شکل Native بسیار ساده و هوشمندانه است؛ کوئری و داده های اصلی اش به صورت جداگانه به سمت سرور ارسال می شوند و احتمال ندارد که بینشان تداخلی به وجود بیاید بنابراین SQL Injection غیر ممکن می شود. البته باید گفت که این نوعِ استفاده محدودیت های خود را نیز دارد چرا که تنها از دو نوع literal پشتیبانی می کنند (رشته ها و اعداد) و برای استفاده در دنیای واقعی کافی نیستند.
در مورد native prepared statement ها باورهای غلطی وجود دارد:
از اول مقاله نیز بحث ما همین بوده است؛ ساختن یک کوئری با قسمت های ثابت و placeholder هایی که بعدا جایشان را به داده های اصلی می دهند! این نوع کوئری ها به صورت خودکار قالب بندی می شوند.
بهترین و برترین مزیت استفاده از prepared statement ها حذف خطر تزریق SQL است:
در واقع دلیل تنفر و دوری از قالب بندی دستی و تعریف و تمجید از prepared statement ها همین موارد است. استفاده از prepared statement ها دو مزیت دیگر نیز دارد که حیاتی نیستند اما باز هم کمک خوبی هستند:
بنابراین Native بودن یک prepared statement حیاتی نیست، همانطور که در سری آموزش PDO دیدیم، می توانیم prepared statement ها را emulate (شبیه سازی) کنیم و با روشن بودن PDO::ATTR_EMULATE_PREPARES
(تنظیم روی true) کوئری را یکجا به سرور بفرستیم. در این حالت داده ها باز هم به صورت صحیح قالب بندی می شوند و می توان گفت که امن است.
حتی با اکستنشن (افزونه) قدیمی MySQL نیز می توان از prepared statement ها استفاده کرد. تابع زیر می تواند با استفاده از extension مذکور نهایت امنیت را به شما بدهد:
function paraQuery() { $args = func_get_args(); $query = array_shift($args); $query = str_replace("%s","'%s'",$query); foreach ($args as $key => $val) { $args[$key] = mysql_real_escape_string($val); } $query = vsprintf($query, $args); $result = mysql_query($query); if (!$result) { throw new Exception(mysql_error()." [$query]"); } return $result; } $query = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d"; $result = paraQuery($query, $a, "%$b%", $limit);
همانطور که می بینید در این مثال نیز امنیت در سطح PDO رعایت شده و همه چیز پارامتری تعیین شده است. بنابراین قانون همیشگی ما این است:
تمام مقادیر پویا و دینامیک (یعنی مقادیری که تغییر می کنند، مانند متغیر ها) باید با prepared statement ها وارد کوئری شوند. چرا مقادیر پویا؟ به این دلیل که قسمت های ثابت یک کوئری هیچ وقت تغییر نمی کنند و هر مشکلی در این زمینه در هنگام توسعه و کدنویسی وب سایت مشخص خواهد شد. بنابراین نیازی به دستکاری قسمت های ثابت وجود ندارد. در مثال بالا SELECT و FROM ثابت هستند. آیا می توانید حالتی را تصور کنید که باعث مشکل شوند؟ خیر!
هدف من از پیش کشیدن این بحث چه بود؟
هدف من این بود که متوجه شوید امنیت اصلی متعلق به بحث prepared statement ها است و مخصوص PDO نیست اما پیشنهاد ما استفاده از PDO است. می توانید به دوره ی آموزش PDO در همین سایت مراجعه کنید.
بحث را در این قسمت خاتمه می دهم. در قسمت بعد در مورد identifier ها و امنیت آن ها صحبت خواهیم کرد.
امیدوارم از این قسمت استفاده ی کافی را برده باشید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.